Merge "Add self revocation public API"
diff --git a/apex/media/OWNERS b/apex/media/OWNERS
index bed3895..2c5965c 100644
--- a/apex/media/OWNERS
+++ b/apex/media/OWNERS
@@ -1,6 +1,5 @@
 # Bug component: 1344
 hdmoon@google.com
-hkuang@google.com
 jinpark@google.com
 klhyun@google.com
 lnilsson@google.com
diff --git a/apex/media/service/java/com/android/server/media/MediaCommunicationService.java b/apex/media/service/java/com/android/server/media/MediaCommunicationService.java
index e48f234..7d47e25 100644
--- a/apex/media/service/java/com/android/server/media/MediaCommunicationService.java
+++ b/apex/media/service/java/com/android/server/media/MediaCommunicationService.java
@@ -79,7 +79,7 @@
 
     final Executor mRecordExecutor = Executors.newSingleThreadExecutor();
     @GuardedBy("mLock")
-    final List<CallbackRecord> mCallbackRecords = new ArrayList<>();
+    final ArrayList<CallbackRecord> mCallbackRecords = new ArrayList<>();
     final NotificationManager mNotificationManager;
     MediaSessionManager mSessionManager;
 
@@ -150,8 +150,8 @@
         return null;
     }
 
-    List<Session2Token> getSession2TokensLocked(int userId) {
-        List<Session2Token> list = new ArrayList<>();
+    ArrayList<Session2Token> getSession2TokensLocked(int userId) {
+        ArrayList<Session2Token> list = new ArrayList<>();
         if (userId == ALL.getIdentifier()) {
             int size = mUserRecords.size();
             for (int i = 0; i < size; i++) {
@@ -237,28 +237,29 @@
     }
 
     void dispatchSession2Changed(int userId) {
-        MediaParceledListSlice<Session2Token> allSession2Tokens;
-        MediaParceledListSlice<Session2Token> userSession2Tokens;
+        ArrayList<Session2Token> allSession2Tokens;
+        ArrayList<Session2Token> userSession2Tokens;
 
         synchronized (mLock) {
-            allSession2Tokens =
-                    new MediaParceledListSlice<>(getSession2TokensLocked(ALL.getIdentifier()));
-            userSession2Tokens = new MediaParceledListSlice<>(getSession2TokensLocked(userId));
-        }
-        allSession2Tokens.setInlineCountLimit(1);
-        userSession2Tokens.setInlineCountLimit(1);
+            allSession2Tokens = getSession2TokensLocked(ALL.getIdentifier());
+            userSession2Tokens = getSession2TokensLocked(userId);
 
-        synchronized (mLock) {
             for (CallbackRecord record : mCallbackRecords) {
                 if (record.mUserId == ALL.getIdentifier()) {
                     try {
-                        record.mCallback.onSession2Changed(allSession2Tokens);
+                        MediaParceledListSlice<Session2Token> toSend =
+                                new MediaParceledListSlice<>(allSession2Tokens);
+                        toSend.setInlineCountLimit(0);
+                        record.mCallback.onSession2Changed(toSend);
                     } catch (RemoteException e) {
                         Log.w(TAG, "Failed to notify session2 tokens changed " + record);
                     }
                 } else if (record.mUserId == userId) {
                     try {
-                        record.mCallback.onSession2Changed(userSession2Tokens);
+                        MediaParceledListSlice<Session2Token> toSend =
+                                new MediaParceledListSlice<>(userSession2Tokens);
+                        toSend.setInlineCountLimit(0);
+                        record.mCallback.onSession2Changed(toSend);
                     } catch (RemoteException e) {
                         Log.w(TAG, "Failed to notify session2 tokens changed " + record);
                     }
@@ -382,7 +383,7 @@
             try {
                 // Check that they can make calls on behalf of the user and get the final user id
                 int resolvedUserId = handleIncomingUser(pid, uid, userId, null);
-                List<Session2Token> result;
+                ArrayList<Session2Token> result;
                 synchronized (mLock) {
                     result = getSession2TokensLocked(resolvedUserId);
                 }
diff --git a/core/api/current.txt b/core/api/current.txt
index 2dfae26..e702dad 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -27364,11 +27364,13 @@
     method @NonNull public android.net.VpnService.Builder addDnsServer(@NonNull java.net.InetAddress);
     method @NonNull public android.net.VpnService.Builder addDnsServer(@NonNull String);
     method @NonNull public android.net.VpnService.Builder addRoute(@NonNull java.net.InetAddress, int);
+    method @NonNull public android.net.VpnService.Builder addRoute(@NonNull android.net.IpPrefix);
     method @NonNull public android.net.VpnService.Builder addRoute(@NonNull String, int);
     method @NonNull public android.net.VpnService.Builder addSearchDomain(@NonNull String);
     method @NonNull public android.net.VpnService.Builder allowBypass();
     method @NonNull public android.net.VpnService.Builder allowFamily(int);
     method @Nullable public android.os.ParcelFileDescriptor establish();
+    method @NonNull public android.net.VpnService.Builder excludeRoute(@NonNull android.net.IpPrefix);
     method @NonNull public android.net.VpnService.Builder setBlocking(boolean);
     method @NonNull public android.net.VpnService.Builder setConfigureIntent(@NonNull android.app.PendingIntent);
     method @NonNull public android.net.VpnService.Builder setHttpProxy(@NonNull android.net.ProxyInfo);
@@ -27686,6 +27688,23 @@
 
 package android.net.vcn {
 
+  public final class VcnCellUnderlyingNetworkTemplate extends android.net.vcn.VcnUnderlyingNetworkTemplate {
+    method @NonNull public java.util.Set<java.lang.String> getOperatorPlmnIds();
+    method public int getOpportunistic();
+    method public int getRoaming();
+    method @NonNull public java.util.Set<java.lang.Integer> getSimSpecificCarrierIds();
+  }
+
+  public static final class VcnCellUnderlyingNetworkTemplate.Builder {
+    ctor public VcnCellUnderlyingNetworkTemplate.Builder();
+    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate build();
+    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setMetered(int);
+    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setOperatorPlmnIds(@NonNull java.util.Set<java.lang.String>);
+    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setOpportunistic(int);
+    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setRoaming(int);
+    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setSimSpecificCarrierIds(@NonNull java.util.Set<java.lang.Integer>);
+  }
+
   public final class VcnConfig implements android.os.Parcelable {
     method public int describeContents();
     method @NonNull public java.util.Set<android.net.vcn.VcnGatewayConnectionConfig> getGatewayConnectionConfigs();
@@ -27704,6 +27723,7 @@
     method @NonNull public String getGatewayConnectionName();
     method @IntRange(from=0x500) public int getMaxMtu();
     method @NonNull public long[] getRetryIntervalsMillis();
+    method @NonNull public java.util.List<android.net.vcn.VcnUnderlyingNetworkTemplate> getVcnUnderlyingNetworkPriorities();
   }
 
   public static final class VcnGatewayConnectionConfig.Builder {
@@ -27713,6 +27733,7 @@
     method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder removeExposedCapability(int);
     method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setMaxMtu(@IntRange(from=0x500) int);
     method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setRetryIntervalsMillis(@NonNull long[]);
+    method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setVcnUnderlyingNetworkPriorities(@NonNull java.util.List<android.net.vcn.VcnUnderlyingNetworkTemplate>);
   }
 
   public class VcnManager {
@@ -27736,6 +27757,24 @@
     method public abstract void onStatusChanged(int);
   }
 
+  public abstract class VcnUnderlyingNetworkTemplate {
+    method public int getMetered();
+    field public static final int MATCH_ANY = 0; // 0x0
+    field public static final int MATCH_FORBIDDEN = 2; // 0x2
+    field public static final int MATCH_REQUIRED = 1; // 0x1
+  }
+
+  public final class VcnWifiUnderlyingNetworkTemplate extends android.net.vcn.VcnUnderlyingNetworkTemplate {
+    method @NonNull public java.util.Set<java.lang.String> getSsids();
+  }
+
+  public static final class VcnWifiUnderlyingNetworkTemplate.Builder {
+    ctor public VcnWifiUnderlyingNetworkTemplate.Builder();
+    method @NonNull public android.net.vcn.VcnWifiUnderlyingNetworkTemplate build();
+    method @NonNull public android.net.vcn.VcnWifiUnderlyingNetworkTemplate.Builder setMetered(int);
+    method @NonNull public android.net.vcn.VcnWifiUnderlyingNetworkTemplate.Builder setSsids(@NonNull java.util.Set<java.lang.String>);
+  }
+
 }
 
 package android.nfc {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 18dae37..e1c05e2 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -1047,6 +1047,7 @@
     field @Deprecated public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL";
     field public static final String EXTRA_PROVISIONING_ORGANIZATION_NAME = "android.app.extra.PROVISIONING_ORGANIZATION_NAME";
     field public static final String EXTRA_PROVISIONING_RETURN_BEFORE_POLICY_COMPLIANCE = "android.app.extra.PROVISIONING_RETURN_BEFORE_POLICY_COMPLIANCE";
+    field public static final String EXTRA_PROVISIONING_ROLE_HOLDER_CUSTOM_USER_CONSENT_INTENT = "android.app.extra.PROVISIONING_ROLE_HOLDER_CUSTOM_USER_CONSENT_INTENT";
     field public static final String EXTRA_PROVISIONING_SKIP_OWNERSHIP_DISCLAIMER = "android.app.extra.PROVISIONING_SKIP_OWNERSHIP_DISCLAIMER";
     field public static final String EXTRA_PROVISIONING_SUPPORTED_MODES = "android.app.extra.PROVISIONING_SUPPORTED_MODES";
     field public static final String EXTRA_PROVISIONING_SUPPORT_URL = "android.app.extra.PROVISIONING_SUPPORT_URL";
@@ -2430,6 +2431,25 @@
 
 package android.bluetooth.le {
 
+  public final class AdvertiseSettings implements android.os.Parcelable {
+    method public int getOwnAddressType();
+  }
+
+  public static final class AdvertiseSettings.Builder {
+    method @NonNull public android.bluetooth.le.AdvertiseSettings.Builder setOwnAddressType(int);
+  }
+
+  public final class AdvertisingSetParameters implements android.os.Parcelable {
+    method public int getOwnAddressType();
+    field public static final int ADDRESS_TYPE_DEFAULT = -1; // 0xffffffff
+    field public static final int ADDRESS_TYPE_PUBLIC = 0; // 0x0
+    field public static final int ADDRESS_TYPE_RANDOM = 1; // 0x1
+  }
+
+  public static final class AdvertisingSetParameters.Builder {
+    method @NonNull public android.bluetooth.le.AdvertisingSetParameters.Builder setOwnAddressType(int);
+  }
+
   public final class BluetoothLeScanner {
     method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_SCAN, android.Manifest.permission.UPDATE_DEVICE_STATS}) public void startScanFromSource(android.os.WorkSource, android.bluetooth.le.ScanCallback);
     method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_SCAN, android.Manifest.permission.UPDATE_DEVICE_STATS}) public void startScanFromSource(java.util.List<android.bluetooth.le.ScanFilter>, android.bluetooth.le.ScanSettings, android.os.WorkSource, android.bluetooth.le.ScanCallback);
@@ -10800,7 +10820,7 @@
 
   public class GameService extends android.app.Service {
     ctor public GameService();
-    method @Nullable public android.os.IBinder onBind(@Nullable android.content.Intent);
+    method @Nullable public final android.os.IBinder onBind(@Nullable android.content.Intent);
     method public void onConnected();
     method public void onDisconnected();
     field public static final String ACTION_GAME_SERVICE = "android.service.games.action.GAME_SERVICE";
@@ -10815,7 +10835,7 @@
 
   public abstract class GameSessionService extends android.app.Service {
     ctor public GameSessionService();
-    method @Nullable public android.os.IBinder onBind(@Nullable android.content.Intent);
+    method @Nullable public final android.os.IBinder onBind(@Nullable android.content.Intent);
     method @NonNull public abstract android.service.games.GameSession onNewSession(@NonNull android.service.games.CreateGameSessionRequest);
     field public static final String ACTION_GAME_SESSION_SERVICE = "android.service.games.action.GAME_SESSION_SERVICE";
   }
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index b052bc5..a9ec11e 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -178,17 +178,16 @@
     Point getAppTaskThumbnailSize();
     /**
      * Only callable from the system. This token grants a temporary permission to call
-     * #startActivityAsCallerWithToken. The token will time out after
-     * START_AS_CALLER_TOKEN_TIMEOUT if it is not used.
+     * #startActivityAsCaller. The token will time out after START_AS_CALLER_TOKEN_TIMEOUT
+     * if it is not used.
      *
-     * @param delegatorToken The Binder token referencing the system Activity that wants to delegate
-     *        the #startActivityAsCaller to another app. The "caller" will be the caller of this
-     *        activity's token, not the delegate's caller (which is probably the delegator itself).
+     * @param componentName The component name of the delegated component that is allowed to
+     *                      call #startActivityAsCaller with the returned token.
      *
      * @return Returns a token that can be given to a "delegate" app that may call
      *         #startActivityAsCaller
      */
-    IBinder requestStartActivityPermissionToken(in IBinder delegatorToken);
+    IBinder requestStartActivityPermissionToken(in ComponentName componentName);
 
     oneway void releaseSomeActivities(in IApplicationThread app);
     Bitmap getTaskDescriptionIcon(in String filename, int userId);
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 7969cda..73ebdf6 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -2885,6 +2885,38 @@
     public static final int RESULT_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER_UNRECOVERABLE_ERROR = 2;
 
     /**
+     * An {@link Intent} extra which resolves to a custom user consent screen.
+     *
+     * <p>If this extra is provided to the device management role holder via either {@link
+     * #ACTION_ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE} or {@link
+     * #ACTION_ROLE_HOLDER_PROVISION_MANAGED_PROFILE}, the device management role holder must
+     * launch this intent which shows the custom user consent screen, replacing its own standard
+     * consent screen.
+     *
+     * <p>If this extra is provided, it is the responsibility of the intent handler to show the
+     * list of disclaimers which are normally shown by the standard consent screen:
+     * <ul>
+     *     <li>Disclaimers set by the IT admin via the {@link #EXTRA_PROVISIONING_DISCLAIMERS}
+     *     provisioning extra</li>
+     *     <li>For fully-managed device provisioning, disclaimers defined in system apps via the
+     *     {@link #EXTRA_PROVISIONING_DISCLAIMER_HEADER} and {@link
+     *     #EXTRA_PROVISIONING_DISCLAIMER_CONTENT} metadata in their manifests</li>
+     *     <li>General disclaimer relevant to the provisioning mode</li>
+     * </ul>
+     *
+     * <p>If the custom consent screens are granted by the user {@link Activity#RESULT_OK} is
+     * returned, otherwise {@link Activity#RESULT_CANCELED} is returned. The device management
+     * role holder should ensure that the provisioning flow terminates immediately if consent
+     * is not granted by the user.
+     *
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    @SystemApi
+    public static final String EXTRA_PROVISIONING_ROLE_HOLDER_CUSTOM_USER_CONSENT_INTENT =
+            "android.app.extra.PROVISIONING_ROLE_HOLDER_CUSTOM_USER_CONSENT_INTENT";
+
+    /**
      * Maximum supported password length. Kind-of arbitrary.
      * @hide
      */
diff --git a/core/java/android/app/trust/TrustManager.java b/core/java/android/app/trust/TrustManager.java
index 65b2775..177de83 100644
--- a/core/java/android/app/trust/TrustManager.java
+++ b/core/java/android/app/trust/TrustManager.java
@@ -156,7 +156,7 @@
 
                 @Override
                 public void onTrustError(CharSequence message) {
-                    Message m = mHandler.obtainMessage(MSG_TRUST_ERROR);
+                    Message m = mHandler.obtainMessage(MSG_TRUST_ERROR, trustListener);
                     m.getData().putCharSequence(DATA_MESSAGE, message);
                     m.sendToTarget();
                 }
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 856a8e1..7e5e96d 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -3064,6 +3064,9 @@
             BluetoothCsipSetCoordinator csipSetCoordinator =
                     new BluetoothCsipSetCoordinator(context, listener, this);
             return true;
+        } else if (profile == BluetoothProfile.LE_CALL_CONTROL) {
+            BluetoothLeCallControl tbs = new BluetoothLeCallControl(context, listener);
+            return true;
         } else {
             return false;
         }
@@ -3166,6 +3169,10 @@
                         (BluetoothCsipSetCoordinator) proxy;
                 csipSetCoordinator.close();
                 break;
+            case BluetoothProfile.LE_CALL_CONTROL:
+                BluetoothLeCallControl tbs = (BluetoothLeCallControl) proxy;
+                tbs.close();
+                break;
         }
     }
 
diff --git a/core/java/android/bluetooth/BluetoothLeCall.java b/core/java/android/bluetooth/BluetoothLeCall.java
new file mode 100644
index 0000000..fb7789d
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothLeCall.java
@@ -0,0 +1,285 @@
+/*
+ * 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.annotation.IntDef;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.ParcelUuid;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+import java.util.UUID;
+
+/**
+ * Representation of Call
+ *
+ * @hide
+ */
+public final class BluetoothLeCall implements Parcelable {
+
+    /** @hide */
+    @IntDef(prefix = "STATE_", value = {
+            STATE_INCOMING,
+            STATE_DIALING,
+            STATE_ALERTING,
+            STATE_ACTIVE,
+            STATE_LOCALLY_HELD,
+            STATE_REMOTELY_HELD,
+            STATE_LOCALLY_AND_REMOTELY_HELD
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface State {
+    }
+
+    /**
+     * A remote party is calling (incoming call).
+     *
+     * @hide
+     */
+    public static final int STATE_INCOMING = 0x00;
+
+    /**
+     * The process to call the remote party has started but the remote party is not
+     * being alerted (outgoing call).
+     *
+     * @hide
+     */
+    public static final int STATE_DIALING = 0x01;
+
+    /**
+     * A remote party is being alerted (outgoing call).
+     *
+     * @hide
+     */
+    public static final int STATE_ALERTING = 0x02;
+
+    /**
+     * The call is in an active conversation.
+     *
+     * @hide
+     */
+    public static final int STATE_ACTIVE = 0x03;
+
+    /**
+     * The call is connected but held locally. “Locally Held” implies that either
+     * the server or the client can affect the state.
+     *
+     * @hide
+     */
+    public static final int STATE_LOCALLY_HELD = 0x04;
+
+    /**
+     * The call is connected but held remotely. “Remotely Held” means that the state
+     * is controlled by the remote party of a call.
+     *
+     * @hide
+     */
+    public static final int STATE_REMOTELY_HELD = 0x05;
+
+    /**
+     * The call is connected but held both locally and remotely.
+     *
+     * @hide
+     */
+    public static final int STATE_LOCALLY_AND_REMOTELY_HELD = 0x06;
+
+    /**
+     * Whether the call direction is outgoing.
+     *
+     * @hide
+     */
+    public static final int FLAG_OUTGOING_CALL = 0x00000001;
+
+    /**
+     * Whether the call URI and Friendly Name are withheld by server.
+     *
+     * @hide
+     */
+    public static final int FLAG_WITHHELD_BY_SERVER = 0x00000002;
+
+    /**
+     * Whether the call URI and Friendly Name are withheld by network.
+     *
+     * @hide
+     */
+    public static final int FLAG_WITHHELD_BY_NETWORK = 0x00000004;
+
+    /** Unique UUID that identifies this call */
+    private UUID mUuid;
+
+    /** Remote Caller URI */
+    private String mUri;
+
+    /** Caller friendly name */
+    private String mFriendlyName;
+
+    /** Call state */
+    private @State int mState;
+
+    /** Call flags */
+    private int mCallFlags;
+
+    /** @hide */
+    public BluetoothLeCall(@NonNull BluetoothLeCall that) {
+        mUuid = new UUID(that.getUuid().getMostSignificantBits(),
+                that.getUuid().getLeastSignificantBits());
+        mUri = that.mUri;
+        mFriendlyName = that.mFriendlyName;
+        mState = that.mState;
+        mCallFlags = that.mCallFlags;
+    }
+
+    /** @hide */
+    public BluetoothLeCall(@NonNull UUID uuid, @NonNull String uri, @NonNull String friendlyName,
+            @State int state, int callFlags) {
+        mUuid = uuid;
+        mUri = uri;
+        mFriendlyName = friendlyName;
+        mState = state;
+        mCallFlags = callFlags;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o)
+            return true;
+        if (o == null || getClass() != o.getClass())
+            return false;
+        BluetoothLeCall that = (BluetoothLeCall) o;
+        return mUuid.equals(that.mUuid) && mUri.equals(that.mUri)
+                && mFriendlyName.equals(that.mFriendlyName) && mState == that.mState
+                && mCallFlags == that.mCallFlags;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mUuid, mUri, mFriendlyName, mState, mCallFlags);
+    }
+
+    /**
+     * Returns a string representation of this BluetoothLeCall.
+     *
+     * <p>
+     * Currently this is the UUID.
+     *
+     * @return string representation of this BluetoothLeCall
+     */
+    @Override
+    public String toString() {
+        return mUuid.toString();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel out, int flags) {
+        out.writeParcelable(new ParcelUuid(mUuid), 0);
+        out.writeString(mUri);
+        out.writeString(mFriendlyName);
+        out.writeInt(mState);
+        out.writeInt(mCallFlags);
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<BluetoothLeCall> CREATOR =
+    						    new Parcelable.Creator<BluetoothLeCall>() {
+        public BluetoothLeCall createFromParcel(Parcel in) {
+            return new BluetoothLeCall(in);
+        }
+
+        public BluetoothLeCall[] newArray(int size) {
+            return new BluetoothLeCall[size];
+        }
+    };
+
+    private BluetoothLeCall(Parcel in) {
+        mUuid = ((ParcelUuid) in.readParcelable(null)).getUuid();
+        mUri = in.readString();
+        mFriendlyName = in.readString();
+        mState = in.readInt();
+        mCallFlags = in.readInt();
+    }
+
+    /**
+     * Returns an UUID of this BluetoothLeCall.
+     *
+     * <p>
+     * An UUID is unique identifier of a BluetoothLeCall.
+     *
+     * @return UUID of this BluetoothLeCall
+     * @hide
+     */
+    public @NonNull UUID getUuid() {
+        return mUuid;
+    }
+
+    /**
+     * Returns a URI of the remote party of this BluetoothLeCall.
+     *
+     * @return string representation of this BluetoothLeCall
+     * @hide
+     */
+    public @NonNull String getUri() {
+        return mUri;
+    }
+
+    /**
+     * Returns a friendly name of the call.
+     *
+     * @return friendly name representation of this BluetoothLeCall
+     * @hide
+     */
+    public @NonNull String getFriendlyName() {
+        return mFriendlyName;
+    }
+
+    /**
+     * Returns the call state.
+     *
+     * @return the state of this BluetoothLeCall
+     * @hide
+     */
+    public @State int getState() {
+        return mState;
+    }
+
+    /**
+     * Returns the call flags.
+     *
+     * @return call flags
+     * @hide
+     */
+    public int getCallFlags() {
+        return mCallFlags;
+    }
+
+    /**
+     * Whether the call direction is incoming.
+     *
+     * @return true if incoming call, false otherwise
+     * @hide
+     */
+    public boolean isIncomingCall() {
+        return (mCallFlags & FLAG_OUTGOING_CALL) == 0;
+    }
+}
diff --git a/core/java/android/bluetooth/BluetoothLeCallControl.java b/core/java/android/bluetooth/BluetoothLeCallControl.java
new file mode 100644
index 0000000..fb080c9
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothLeCallControl.java
@@ -0,0 +1,899 @@
+/*
+ * Copyright 2019 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.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.ParcelUuid;
+import android.os.RemoteException;
+import android.util.Log;
+import android.annotation.SuppressLint;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.Executor;
+
+/**
+ * This class provides the APIs to control the Call Control profile.
+ *
+ * <p>
+ * This class provides Bluetooth Telephone Bearer Service functionality,
+ * allowing applications to expose a GATT Service based interface to control the
+ * state of the calls by remote devices such as LE audio devices.
+ *
+ * <p>
+ * BluetoothLeCallControl is a proxy object for controlling the Bluetooth Telephone Bearer
+ * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get the
+ * BluetoothLeCallControl proxy object.
+ *
+ * @hide
+ */
+public final class BluetoothLeCallControl implements BluetoothProfile {
+    private static final String TAG = "BluetoothLeCallControl";
+    private static final boolean DBG = true;
+    private static final boolean VDBG = false;
+
+    /** @hide */
+    @IntDef(prefix = "RESULT_", value = {
+            RESULT_SUCCESS,
+            RESULT_ERROR_UNKNOWN_CALL_ID,
+            RESULT_ERROR_INVALID_URI,
+            RESULT_ERROR_APPLICATION
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Result {
+    }
+
+    /**
+     * Opcode write was successful.
+     *
+     * @hide
+     */
+    public static final int RESULT_SUCCESS = 0;
+
+    /**
+     * Unknown call Id has been used in the operation.
+     *
+     * @hide
+     */
+    public static final int RESULT_ERROR_UNKNOWN_CALL_ID = 1;
+
+    /**
+     * The URI provided in {@link Callback#onPlaceCallRequest} is invalid.
+     *
+     * @hide
+     */
+    public static final int RESULT_ERROR_INVALID_URI = 2;
+
+    /**
+     * Application internal error.
+     *
+     * @hide
+     */
+    public static final int RESULT_ERROR_APPLICATION = 3;
+
+    /** @hide */
+    @IntDef(prefix = "TERMINATION_REASON_", value = {
+            TERMINATION_REASON_INVALID_URI,
+            TERMINATION_REASON_FAIL,
+            TERMINATION_REASON_REMOTE_HANGUP,
+            TERMINATION_REASON_SERVER_HANGUP,
+            TERMINATION_REASON_LINE_BUSY,
+            TERMINATION_REASON_NETWORK_CONGESTION,
+            TERMINATION_REASON_CLIENT_HANGUP,
+            TERMINATION_REASON_NO_SERVICE,
+            TERMINATION_REASON_NO_ANSWER
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface TerminationReason {
+    }
+
+    /**
+     * Remote Caller ID value used to place a call was formed improperly.
+     *
+     * @hide
+     */
+    public static final int TERMINATION_REASON_INVALID_URI = 0x00;
+
+    /**
+     * Call fail.
+     *
+     * @hide
+     */
+    public static final int TERMINATION_REASON_FAIL = 0x01;
+
+    /**
+     * Remote party ended call.
+     *
+     * @hide
+     */
+    public static final int TERMINATION_REASON_REMOTE_HANGUP = 0x02;
+
+    /**
+     * Call ended from the server.
+     *
+     * @hide
+     */
+    public static final int TERMINATION_REASON_SERVER_HANGUP = 0x03;
+
+    /**
+     * Line busy.
+     *
+     * @hide
+     */
+    public static final int TERMINATION_REASON_LINE_BUSY = 0x04;
+
+    /**
+     * Network congestion.
+     *
+     * @hide
+     */
+    public static final int TERMINATION_REASON_NETWORK_CONGESTION = 0x05;
+
+    /**
+     * Client terminated.
+     *
+     * @hide
+     */
+    public static final int TERMINATION_REASON_CLIENT_HANGUP = 0x06;
+
+    /**
+     * No service.
+     *
+     * @hide
+     */
+    public static final int TERMINATION_REASON_NO_SERVICE = 0x07;
+
+    /**
+     * No answer.
+     *
+     * @hide
+     */
+    public static final int TERMINATION_REASON_NO_ANSWER = 0x08;
+
+    /*
+     * Flag indicating support for hold/unhold call feature.
+     *
+     * @hide
+     */
+    public static final int CAPABILITY_HOLD_CALL = 0x00000001;
+
+    /**
+     * Flag indicating support for joining calls feature.
+     *
+     * @hide
+     */
+    public static final int CAPABILITY_JOIN_CALLS = 0x00000002;
+
+    private static final int MESSAGE_TBS_SERVICE_CONNECTED = 102;
+    private static final int MESSAGE_TBS_SERVICE_DISCONNECTED = 103;
+
+    private static final int REG_TIMEOUT = 10000;
+
+    /**
+     * The template class is used to call callback functions on events from the TBS
+     * server. Callback functions are wrapped in this class and registered to the
+     * Android system during app registration.
+     *
+     * @hide
+     */
+    public abstract static class Callback {
+
+        private static final String TAG = "BluetoothLeCallControl.Callback";
+
+        /**
+         * Called when a remote client requested to accept the call.
+         *
+         * <p>
+         * An application must call {@link BluetoothLeCallControl#requestResult} to complete the
+         * request.
+         *
+         * @param requestId The Id of the request
+         * @param callId    The call Id requested to be accepted
+         * @hide
+         */
+        public abstract void onAcceptCall(int requestId, @NonNull UUID callId);
+
+        /**
+         * A remote client has requested to terminate the call.
+         *
+         * <p>
+         * An application must call {@link BluetoothLeCallControl#requestResult} to complete the
+         * request.
+         *
+         * @param requestId The Id of the request
+         * @param callId    The call Id requested to terminate
+         * @hide
+         */
+        public abstract void onTerminateCall(int requestId, @NonNull UUID callId);
+
+        /**
+         * A remote client has requested to hold the call.
+         *
+         * <p>
+         * An application must call {@link BluetoothLeCallControl#requestResult} to complete the
+         * request.
+         *
+         * @param requestId The Id of the request
+         * @param callId    The call Id requested to be put on hold
+         * @hide
+         */
+        public void onHoldCall(int requestId, @NonNull UUID callId) {
+            Log.e(TAG, "onHoldCall: unimplemented, however CAPABILITY_HOLD_CALL is set!");
+        }
+
+        /**
+         * A remote client has requested to unhold the call.
+         *
+         * <p>
+         * An application must call {@link BluetoothLeCallControl#requestResult} to complete the
+         * request.
+         *
+         * @param requestId The Id of the request
+         * @param callId    The call Id requested to unhold
+         * @hide
+         */
+        public void onUnholdCall(int requestId, @NonNull UUID callId) {
+            Log.e(TAG, "onUnholdCall: unimplemented, however CAPABILITY_HOLD_CALL is set!");
+        }
+
+        /**
+         * A remote client has requested to place a call.
+         *
+         * <p>
+         * An application must call {@link BluetoothLeCallControl#requestResult} to complete the
+         * request.
+         *
+         * @param requestId The Id of the request
+         * @param callId    The Id to be assigned for the new call
+         * @param uri       The caller URI requested
+         * @hide
+         */
+        public abstract void onPlaceCall(int requestId, @NonNull UUID callId, @NonNull String uri);
+
+        /**
+         * A remote client has requested to join the calls.
+         *
+         * <p>
+         * An application must call {@link BluetoothLeCallControl#requestResult} to complete the
+         * request.
+         *
+         * @param requestId The Id of the request
+         * @param callIds   The call Id list requested to join
+         * @hide
+         */
+        public void onJoinCalls(int requestId, @NonNull List<UUID> callIds) {
+            Log.e(TAG, "onJoinCalls: unimplemented, however CAPABILITY_JOIN_CALLS is set!");
+        }
+    }
+
+    private class CallbackWrapper extends IBluetoothLeCallControlCallback.Stub {
+
+        private final Executor mExecutor;
+        private final Callback mCallback;
+
+        CallbackWrapper(Executor executor, Callback callback) {
+            mExecutor = executor;
+            mCallback = callback;
+        }
+
+        @Override
+        public void onBearerRegistered(int ccid) {
+            if (mCallback != null) {
+                mCcid = ccid;
+            } else {
+                // registration timeout
+                Log.e(TAG, "onBearerRegistered: mCallback is null");
+            }
+        }
+
+        @Override
+        public void onAcceptCall(int requestId, ParcelUuid uuid) {
+            final long identityToken = Binder.clearCallingIdentity();
+            try {
+                mExecutor.execute(() -> mCallback.onAcceptCall(requestId, uuid.getUuid()));
+            } finally {
+                Binder.restoreCallingIdentity(identityToken);
+            }
+        }
+
+        @Override
+        public void onTerminateCall(int requestId, ParcelUuid uuid) {
+            final long identityToken = Binder.clearCallingIdentity();
+            try {
+                mExecutor.execute(() -> mCallback.onTerminateCall(requestId, uuid.getUuid()));
+            } finally {
+                Binder.restoreCallingIdentity(identityToken);
+            }
+        }
+
+        @Override
+        public void onHoldCall(int requestId, ParcelUuid uuid) {
+            final long identityToken = Binder.clearCallingIdentity();
+            try {
+                mExecutor.execute(() -> mCallback.onHoldCall(requestId, uuid.getUuid()));
+            } finally {
+                Binder.restoreCallingIdentity(identityToken);
+            }
+        }
+
+        @Override
+        public void onUnholdCall(int requestId, ParcelUuid uuid) {
+            final long identityToken = Binder.clearCallingIdentity();
+            try {
+                mExecutor.execute(() -> mCallback.onUnholdCall(requestId, uuid.getUuid()));
+            } finally {
+                Binder.restoreCallingIdentity(identityToken);
+            }
+        }
+
+        @Override
+        public void onPlaceCall(int requestId, ParcelUuid uuid, String uri) {
+            final long identityToken = Binder.clearCallingIdentity();
+            try {
+                mExecutor.execute(() -> mCallback.onPlaceCall(requestId, uuid.getUuid(), uri));
+            } finally {
+                Binder.restoreCallingIdentity(identityToken);
+            }
+        }
+
+        @Override
+        public void onJoinCalls(int requestId, List<ParcelUuid> parcelUuids) {
+            List<UUID> uuids = new ArrayList<>();
+            for (ParcelUuid parcelUuid : parcelUuids) {
+                uuids.add(parcelUuid.getUuid());
+            }
+
+            final long identityToken = Binder.clearCallingIdentity();
+            try {
+                mExecutor.execute(() -> mCallback.onJoinCalls(requestId, uuids));
+            } finally {
+                Binder.restoreCallingIdentity(identityToken);
+            }
+        }
+    };
+
+    private Context mContext;
+    private ServiceListener mServiceListener;
+    private volatile IBluetoothLeCallControl mService;
+    private BluetoothAdapter mAdapter;
+    private int mCcid = 0;
+    private String mToken;
+    private Callback mCallback = null;
+
+    private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
+        new IBluetoothStateChangeCallback.Stub() {
+        public void onBluetoothStateChange(boolean up) {
+            if (DBG)
+                Log.d(TAG, "onBluetoothStateChange: up=" + up);
+            if (!up) {
+                doUnbind();
+            } else {
+                doBind();
+            }
+        }
+    };
+
+    /**
+     * Create a BluetoothLeCallControl proxy object for interacting with the local Bluetooth
+     * telephone bearer service.
+     */
+    /* package */ BluetoothLeCallControl(Context context, ServiceListener listener) {
+        mContext = context;
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
+        mServiceListener = listener;
+
+        IBluetoothManager mgr = mAdapter.getBluetoothManager();
+        if (mgr != null) {
+            try {
+                mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
+            } catch (RemoteException e) {
+                Log.e(TAG, "", e);
+            }
+        }
+
+        doBind();
+    }
+
+    private boolean doBind() {
+        synchronized (mConnection) {
+            if (mService == null) {
+                if (VDBG)
+                    Log.d(TAG, "Binding service...");
+                try {
+                    return mAdapter.getBluetoothManager().
+                            bindBluetoothProfileService(BluetoothProfile.LE_CALL_CONTROL,
+                            mConnection);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Unable to bind TelephoneBearerService", e);
+                }
+            }
+        }
+        return false;
+    }
+
+    private void doUnbind() {
+        synchronized (mConnection) {
+            if (mService != null) {
+                if (VDBG)
+                    Log.d(TAG, "Unbinding service...");
+                try {
+                    mAdapter.getBluetoothManager().
+                        unbindBluetoothProfileService(BluetoothProfile.LE_CALL_CONTROL,
+                        mConnection);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Unable to unbind TelephoneBearerService", e);
+                } finally {
+                    mService = null;
+                }
+            }
+        }
+    }
+
+    /* package */ void close() {
+        if (VDBG)
+            log("close()");
+        unregisterBearer();
+
+        IBluetoothManager mgr = mAdapter.getBluetoothManager();
+        if (mgr != null) {
+            try {
+                mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
+            } catch (RemoteException re) {
+                Log.e(TAG, "", re);
+            }
+        }
+        mServiceListener = null;
+        doUnbind();
+    }
+
+    private IBluetoothLeCallControl getService() {
+        return mService;
+    }
+
+    /**
+     * Not supported
+     *
+     * @throws UnsupportedOperationException
+     */
+    @Override
+    public int getConnectionState(@Nullable BluetoothDevice device) {
+        throw new UnsupportedOperationException("not supported");
+    }
+
+    /**
+     * Not supported
+     *
+     * @throws UnsupportedOperationException
+     */
+    @Override
+    public @NonNull List<BluetoothDevice> getConnectedDevices() {
+        throw new UnsupportedOperationException("not supported");
+    }
+
+    /**
+     * Not supported
+     *
+     * @throws UnsupportedOperationException
+     */
+    @Override
+    public @NonNull List<BluetoothDevice> getDevicesMatchingConnectionStates(
+        @NonNull int[] states) {
+        throw new UnsupportedOperationException("not supported");
+    }
+
+    /**
+     * Register Telephone Bearer exposing the interface that allows remote devices
+     * to track and control the call states.
+     *
+     * <p>
+     * This is an asynchronous call. The callback is used to notify success or
+     * failure if the function returns true.
+     *
+     * <p>
+     * Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * <!-- The UCI is a String identifier of the telephone bearer as defined at
+     * https://www.bluetooth.com/specifications/assigned-numbers/uniform-caller-identifiers
+     * (login required). -->
+     *
+     * <!-- The examples of common URI schemes can be found in
+     * https://iana.org/assignments/uri-schemes/uri-schemes.xhtml -->
+     *
+     * <!-- The Technology is an integer value. The possible values are defined at
+     * https://www.bluetooth.com/specifications/assigned-numbers (login required).
+     * -->
+     *
+     * @param uci          Bearer Unique Client Identifier
+     * @param uriSchemes   URI Schemes supported list
+     * @param capabilities bearer capabilities
+     * @param provider     Network provider name
+     * @param technology   Network technology
+     * @param executor     {@link Executor} object on which callback will be
+     *                     executed. The Executor object is required.
+     * @param callback     {@link Callback} object to which callback messages will
+     *                     be sent. The Callback object is required.
+     * @return true on success, false otherwise
+     * @hide
+     */
+    @SuppressLint("ExecutorRegistration")
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean registerBearer(@Nullable String uci,
+                    @NonNull List<String> uriSchemes, int capabilities,
+                    @NonNull String provider, int technology,
+                    @NonNull Executor executor, @NonNull Callback callback) {
+        if (DBG) {
+            Log.d(TAG, "registerBearer");
+        }
+        if (callback == null) {
+            throw new IllegalArgumentException("null parameter: " + callback);
+        }
+        if (mCcid != 0) {
+            return false;
+        }
+
+        mToken = uci;
+
+        final IBluetoothLeCallControl service = getService();
+        if (service != null) {
+            if (mCallback != null) {
+                Log.e(TAG, "Bearer can be opened only once");
+                return false;
+            }
+
+            mCallback = callback;
+            try {
+                CallbackWrapper callbackWrapper = new CallbackWrapper(executor, callback);
+                service.registerBearer(mToken, callbackWrapper, uci, uriSchemes, capabilities,
+                                        provider, technology);
+            } catch (RemoteException e) {
+                Log.e(TAG, "", e);
+                mCallback = null;
+                return false;
+            }
+
+            if (mCcid == 0) {
+                mCallback = null;
+                return false;
+            }
+
+            return true;
+        }
+
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+        }
+
+        return false;
+    }
+
+    /**
+     * Unregister Telephone Bearer Service and destroy all the associated data.
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public void unregisterBearer() {
+        if (DBG) {
+            Log.d(TAG, "unregisterBearer");
+        }
+        if (mCcid == 0) {
+            return;
+        }
+
+        int ccid = mCcid;
+        mCcid = 0;
+        mCallback = null;
+
+        final IBluetoothLeCallControl service = getService();
+        if (service != null) {
+            try {
+                service.unregisterBearer(mToken);
+            } catch (RemoteException e) {
+                Log.e(TAG, "", e);
+            }
+        }
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+        }
+    }
+
+    /**
+     * Get the Content Control ID (CCID) value.
+     *
+     * @return ccid Content Control ID value
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public int getContentControlId() {
+        return mCcid;
+    }
+
+    /**
+     * Notify about the newly added call.
+     *
+     * <p>
+     * This shall be called as early as possible after the call has been added.
+     *
+     * <p>
+     * Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param call Newly added call
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public void onCallAdded(@NonNull BluetoothLeCall call) {
+        if (DBG) {
+            Log.d(TAG, "onCallAdded: call=" + call);
+        }
+        if (mCcid == 0) {
+            return;
+        }
+
+        final IBluetoothLeCallControl service = getService();
+        if (service != null) {
+            try {
+                service.callAdded(mCcid, call);
+            } catch (RemoteException e) {
+                Log.e(TAG, "", e);
+            }
+        }
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+        }
+    }
+
+    /**
+     * Notify about the removed call.
+     *
+     * <p>
+     * This shall be called as early as possible after the call has been removed.
+     *
+     * <p>
+     * Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param callId The Id of a call that has been removed
+     * @param reason Call termination reason
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public void onCallRemoved(@NonNull UUID callId, @TerminationReason int reason) {
+        if (DBG) {
+            Log.d(TAG, "callRemoved: callId=" + callId);
+        }
+        if (mCcid == 0) {
+            return;
+        }
+
+        final IBluetoothLeCallControl service = getService();
+        if (service != null) {
+            try {
+                service.callRemoved(mCcid, new ParcelUuid(callId), reason);
+            } catch (RemoteException e) {
+                Log.e(TAG, "", e);
+            }
+        }
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+        }
+    }
+
+    /**
+     * Notify the call state change
+     *
+     * <p>
+     * This shall be called as early as possible after the state of the call has
+     * changed.
+     *
+     * <p>
+     * Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param callId The call Id that state has been changed
+     * @param state  Call state
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public void onCallStateChanged(@NonNull UUID callId, @BluetoothLeCall.State int state) {
+        if (DBG) {
+            Log.d(TAG, "callStateChanged: callId=" + callId + " state=" + state);
+        }
+        if (mCcid == 0) {
+            return;
+        }
+
+        final IBluetoothLeCallControl service = getService();
+        if (service != null) {
+            try {
+                service.callStateChanged(mCcid, new ParcelUuid(callId), state);
+            } catch (RemoteException e) {
+                Log.e(TAG, "", e);
+            }
+        }
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+        }
+    }
+
+    /**
+     * Provide the current calls list
+     *
+     * <p>
+     * This function must be invoked after registration if application has any
+     * calls.
+     *
+     * @param calls current calls list
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
+     public void currentCallsList(@NonNull List<BluetoothLeCall> calls) {
+        final IBluetoothLeCallControl service = getService();
+        if (service != null) {
+            try {
+                service.currentCallsList(mCcid, calls);
+            } catch (RemoteException e) {
+                Log.e(TAG, "", e);
+            }
+        }
+    }
+
+    /**
+     * Provide the network current status
+     *
+     * <p>
+     * This function must be invoked on change of network state.
+     *
+     * <p>
+     * Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * <!-- The Technology is an integer value. The possible values are defined at
+     * https://www.bluetooth.com/specifications/assigned-numbers (login required).
+     * -->
+     *
+     * @param provider   Network provider name
+     * @param technology Network technology
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public void networkStateChanged(@NonNull String provider, int technology) {
+        if (DBG) {
+            Log.d(TAG, "networkStateChanged: provider=" + provider + ", technology=" + technology);
+        }
+        if (mCcid == 0) {
+            return;
+        }
+
+        final IBluetoothLeCallControl service = getService();
+        if (service != null) {
+            try {
+                service.networkStateChanged(mCcid, provider, technology);
+            } catch (RemoteException e) {
+                Log.e(TAG, "", e);
+            }
+        }
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+        }
+    }
+
+    /**
+     * Send a response to a call control request to a remote device.
+     *
+     * <p>
+     * This function must be invoked in when a request is received by one of these
+     * callback methods:
+     *
+     * <ul>
+     * <li>{@link Callback#onAcceptCall}
+     * <li>{@link Callback#onTerminateCall}
+     * <li>{@link Callback#onHoldCall}
+     * <li>{@link Callback#onUnholdCall}
+     * <li>{@link Callback#onPlaceCall}
+     * <li>{@link Callback#onJoinCalls}
+     * </ul>
+     *
+     * @param requestId The ID of the request that was received with the callback
+     * @param result    The result of the request to be sent to the remote devices
+     */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public void requestResult(int requestId, @Result int result) {
+        if (DBG) {
+            Log.d(TAG, "requestResult: requestId=" + requestId + " result=" + result);
+        }
+        if (mCcid == 0) {
+            return;
+        }
+
+        final IBluetoothLeCallControl service = getService();
+        if (service != null) {
+            try {
+                service.requestResult(mCcid, requestId, result);
+            } catch (RemoteException e) {
+                Log.e(TAG, "", e);
+            }
+        }
+    }
+
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
+    private static boolean isValidDevice(@Nullable BluetoothDevice device) {
+        return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress());
+    }
+
+    private static void log(String msg) {
+        Log.d(TAG, msg);
+    }
+
+    private final IBluetoothProfileServiceConnection mConnection =
+                                    new IBluetoothProfileServiceConnection.Stub() {
+        @Override
+        public void onServiceConnected(ComponentName className, IBinder service) {
+            if (DBG) {
+                Log.d(TAG, "Proxy object connected");
+            }
+            mService = IBluetoothLeCallControl.Stub.asInterface(Binder.allowBlocking(service));
+            mHandler.sendMessage(mHandler.obtainMessage(MESSAGE_TBS_SERVICE_CONNECTED));
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName className) {
+            if (DBG) {
+                Log.d(TAG, "Proxy object disconnected");
+            }
+            doUnbind();
+            mHandler.sendMessage(mHandler.obtainMessage(MESSAGE_TBS_SERVICE_DISCONNECTED));
+        }
+    };
+
+    private final Handler mHandler = new Handler(Looper.getMainLooper()) {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+            case MESSAGE_TBS_SERVICE_CONNECTED: {
+                if (mServiceListener != null) {
+                    mServiceListener.onServiceConnected(BluetoothProfile.LE_CALL_CONTROL,
+                        BluetoothLeCallControl.this);
+                }
+                break;
+            }
+            case MESSAGE_TBS_SERVICE_DISCONNECTED: {
+                if (mServiceListener != null) {
+                    mServiceListener.onServiceDisconnected(BluetoothProfile.LE_CALL_CONTROL);
+                }
+                break;
+            }
+            }
+        }
+    };
+}
diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java
index e047e5d..d0f74e9 100644
--- a/core/java/android/bluetooth/BluetoothProfile.java
+++ b/core/java/android/bluetooth/BluetoothProfile.java
@@ -240,12 +240,19 @@
     int LE_AUDIO_BROADCAST = 26;
 
     /**
+     * @hide
+     * Telephone Bearer Service from Call Control Profile
+     *
+     */
+    int LE_CALL_CONTROL = 27;
+
+    /**
      * 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 = 26;
+    int MAX_PROFILE_ID = 27;
 
     /**
      * Default priority for devices that we try to auto-connect to and
diff --git a/core/java/android/bluetooth/le/AdvertiseSettings.java b/core/java/android/bluetooth/le/AdvertiseSettings.java
index 7129d76..c52a6ee 100644
--- a/core/java/android/bluetooth/le/AdvertiseSettings.java
+++ b/core/java/android/bluetooth/le/AdvertiseSettings.java
@@ -16,6 +16,9 @@
 
 package android.bluetooth.le;
 
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.bluetooth.le.AdvertisingSetParameters.AddressTypeStatus;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -70,17 +73,21 @@
      */
     private static final int LIMITED_ADVERTISING_MAX_MILLIS = 180 * 1000;
 
+
     private final int mAdvertiseMode;
     private final int mAdvertiseTxPowerLevel;
     private final int mAdvertiseTimeoutMillis;
     private final boolean mAdvertiseConnectable;
+    private final int mOwnAddressType;
 
     private AdvertiseSettings(int advertiseMode, int advertiseTxPowerLevel,
-            boolean advertiseConnectable, int advertiseTimeout) {
+            boolean advertiseConnectable, int advertiseTimeout,
+            @AddressTypeStatus int ownAddressType) {
         mAdvertiseMode = advertiseMode;
         mAdvertiseTxPowerLevel = advertiseTxPowerLevel;
         mAdvertiseConnectable = advertiseConnectable;
         mAdvertiseTimeoutMillis = advertiseTimeout;
+        mOwnAddressType = ownAddressType;
     }
 
     private AdvertiseSettings(Parcel in) {
@@ -88,6 +95,7 @@
         mAdvertiseTxPowerLevel = in.readInt();
         mAdvertiseConnectable = in.readInt() != 0;
         mAdvertiseTimeoutMillis = in.readInt();
+        mOwnAddressType = in.readInt();
     }
 
     /**
@@ -118,12 +126,23 @@
         return mAdvertiseTimeoutMillis;
     }
 
+    /**
+     * @return the own address type for advertising
+     *
+     * @hide
+     */
+    @SystemApi
+    public @AddressTypeStatus int getOwnAddressType() {
+        return mOwnAddressType;
+    }
+
     @Override
     public String toString() {
         return "Settings [mAdvertiseMode=" + mAdvertiseMode
                 + ", mAdvertiseTxPowerLevel=" + mAdvertiseTxPowerLevel
                 + ", mAdvertiseConnectable=" + mAdvertiseConnectable
-                + ", mAdvertiseTimeoutMillis=" + mAdvertiseTimeoutMillis + "]";
+                + ", mAdvertiseTimeoutMillis=" + mAdvertiseTimeoutMillis
+                + ", mOwnAddressType=" + mOwnAddressType + "]";
     }
 
     @Override
@@ -137,6 +156,7 @@
         dest.writeInt(mAdvertiseTxPowerLevel);
         dest.writeInt(mAdvertiseConnectable ? 1 : 0);
         dest.writeInt(mAdvertiseTimeoutMillis);
+        dest.writeInt(mOwnAddressType);
     }
 
     public static final @android.annotation.NonNull Parcelable.Creator<AdvertiseSettings> CREATOR =
@@ -160,6 +180,7 @@
         private int mTxPowerLevel = ADVERTISE_TX_POWER_MEDIUM;
         private int mTimeoutMillis = 0;
         private boolean mConnectable = true;
+        private int mOwnAddressType = AdvertisingSetParameters.ADDRESS_TYPE_DEFAULT;
 
         /**
          * Set advertise mode to control the advertising power and latency.
@@ -226,10 +247,31 @@
         }
 
         /**
+         * Set own address type for advertising to control public or privacy mode. If used to set
+         * address type anything other than {@link AdvertisingSetParameters#ADDRESS_TYPE_DEFAULT},
+         * then it will require BLUETOOTH_PRIVILEGED permission and will be checked at the
+         * time of starting advertising.
+         *
+         * @throws IllegalArgumentException If the {@code ownAddressType} is invalid
+         *
+         * @hide
+         */
+        @SystemApi
+        public @NonNull Builder setOwnAddressType(@AddressTypeStatus int ownAddressType) {
+            if (ownAddressType < AdvertisingSetParameters.ADDRESS_TYPE_DEFAULT
+                    ||  ownAddressType > AdvertisingSetParameters.ADDRESS_TYPE_RANDOM) {
+                throw new IllegalArgumentException("unknown address type " + ownAddressType);
+            }
+            mOwnAddressType = ownAddressType;
+            return this;
+        }
+
+        /**
          * Build the {@link AdvertiseSettings} object.
          */
         public AdvertiseSettings build() {
-            return new AdvertiseSettings(mMode, mTxPowerLevel, mConnectable, mTimeoutMillis);
+            return new AdvertiseSettings(mMode, mTxPowerLevel, mConnectable, mTimeoutMillis,
+                mOwnAddressType);
         }
     }
 }
diff --git a/core/java/android/bluetooth/le/AdvertisingSetParameters.java b/core/java/android/bluetooth/le/AdvertisingSetParameters.java
index e39b198..5c8fae6 100644
--- a/core/java/android/bluetooth/le/AdvertisingSetParameters.java
+++ b/core/java/android/bluetooth/le/AdvertisingSetParameters.java
@@ -16,11 +16,17 @@
 
 package android.bluetooth.le;
 
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * The {@link AdvertisingSetParameters} provide a way to adjust advertising
  * preferences for each
@@ -97,6 +103,39 @@
      */
     private static final int LIMITED_ADVERTISING_MAX_MILLIS = 180 * 1000;
 
+    /** @hide */
+    @IntDef(prefix = "ADDRESS_TYPE_", value = {
+        ADDRESS_TYPE_DEFAULT,
+        ADDRESS_TYPE_PUBLIC,
+        ADDRESS_TYPE_RANDOM
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface AddressTypeStatus {}
+
+    /**
+     * Advertise own address type that corresponds privacy settings of the device.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int ADDRESS_TYPE_DEFAULT = -1;
+
+    /**
+     * Advertise own public address type.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int ADDRESS_TYPE_PUBLIC = 0;
+
+    /**
+     * Generate and adverise own resolvable private address.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int ADDRESS_TYPE_RANDOM = 1;
+
     private final boolean mIsLegacy;
     private final boolean mIsAnonymous;
     private final boolean mIncludeTxPower;
@@ -106,11 +145,12 @@
     private final boolean mScannable;
     private final int mInterval;
     private final int mTxPowerLevel;
+    private final int mOwnAddressType;
 
     private AdvertisingSetParameters(boolean connectable, boolean scannable, boolean isLegacy,
             boolean isAnonymous, boolean includeTxPower,
             int primaryPhy, int secondaryPhy,
-            int interval, int txPowerLevel) {
+            int interval, int txPowerLevel, @AddressTypeStatus int ownAddressType) {
         mConnectable = connectable;
         mScannable = scannable;
         mIsLegacy = isLegacy;
@@ -120,6 +160,7 @@
         mSecondaryPhy = secondaryPhy;
         mInterval = interval;
         mTxPowerLevel = txPowerLevel;
+        mOwnAddressType = ownAddressType;
     }
 
     private AdvertisingSetParameters(Parcel in) {
@@ -132,6 +173,7 @@
         mSecondaryPhy = in.readInt();
         mInterval = in.readInt();
         mTxPowerLevel = in.readInt();
+        mOwnAddressType = in.readInt();
     }
 
     /**
@@ -197,6 +239,16 @@
         return mTxPowerLevel;
     }
 
+    /**
+     * @return the own address type for advertising
+     *
+     * @hide
+     */
+    @SystemApi
+    public @AddressTypeStatus int getOwnAddressType() {
+        return mOwnAddressType;
+    }
+
     @Override
     public String toString() {
         return "AdvertisingSetParameters [connectable=" + mConnectable
@@ -206,7 +258,8 @@
                 + ", primaryPhy=" + mPrimaryPhy
                 + ", secondaryPhy=" + mSecondaryPhy
                 + ", interval=" + mInterval
-                + ", txPowerLevel=" + mTxPowerLevel + "]";
+                + ", txPowerLevel=" + mTxPowerLevel
+                + ", ownAddressType=" + mOwnAddressType + "]";
     }
 
     @Override
@@ -225,6 +278,7 @@
         dest.writeInt(mSecondaryPhy);
         dest.writeInt(mInterval);
         dest.writeInt(mTxPowerLevel);
+        dest.writeInt(mOwnAddressType);
     }
 
     public static final @android.annotation.NonNull Parcelable.Creator<AdvertisingSetParameters> CREATOR =
@@ -253,6 +307,7 @@
         private int mSecondaryPhy = BluetoothDevice.PHY_LE_1M;
         private int mInterval = INTERVAL_LOW;
         private int mTxPowerLevel = TX_POWER_MEDIUM;
+        private int mOwnAddressType = ADDRESS_TYPE_DEFAULT;
 
         /**
          * Set whether the advertisement type should be connectable or
@@ -399,6 +454,26 @@
         }
 
         /**
+         * Set own address type for advertising to control public or privacy mode. If used to set
+         * address type anything other than {@link AdvertisingSetParameters#ADDRESS_TYPE_DEFAULT},
+         * then it will require BLUETOOTH_PRIVILEGED permission and will be checked at the
+         * time of starting advertising.
+         *
+         * @throws IllegalArgumentException If the {@code ownAddressType} is invalid
+         *
+         * @hide
+         */
+        @SystemApi
+        public @NonNull Builder setOwnAddressType(@AddressTypeStatus int ownAddressType) {
+            if (ownAddressType < AdvertisingSetParameters.ADDRESS_TYPE_DEFAULT
+                    ||  ownAddressType > AdvertisingSetParameters.ADDRESS_TYPE_RANDOM) {
+                throw new IllegalArgumentException("unknown address type " + ownAddressType);
+            }
+            mOwnAddressType = ownAddressType;
+            return this;
+        }
+
+        /**
          * Build the {@link AdvertisingSetParameters} object.
          *
          * @throws IllegalStateException if invalid combination of parameters is used.
@@ -431,7 +506,8 @@
             }
 
             return new AdvertisingSetParameters(mConnectable, mScannable, mIsLegacy, mIsAnonymous,
-                    mIncludeTxPower, mPrimaryPhy, mSecondaryPhy, mInterval, mTxPowerLevel);
+                    mIncludeTxPower, mPrimaryPhy, mSecondaryPhy, mInterval, mTxPowerLevel,
+                    mOwnAddressType);
         }
     }
 }
diff --git a/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java b/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
index b9f8a57..879dcee 100644
--- a/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
+++ b/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
@@ -138,6 +138,7 @@
             parameters.setLegacyMode(true);
             parameters.setConnectable(isConnectable);
             parameters.setScannable(true); // legacy advertisements we support are always scannable
+            parameters.setOwnAddressType(settings.getOwnAddressType());
             if (settings.getMode() == AdvertiseSettings.ADVERTISE_MODE_LOW_POWER) {
                 parameters.setInterval(1600); // 1s
             } else if (settings.getMode() == AdvertiseSettings.ADVERTISE_MODE_BALANCED) {
diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java
index 32827ae..b3435b1 100644
--- a/core/java/android/content/IntentFilter.java
+++ b/core/java/android/content/IntentFilter.java
@@ -1182,7 +1182,7 @@
         public int match(Uri data, boolean wildcardSupported) {
             String host = data.getHost();
             if (host == null) {
-                if (wildcardSupported && mWild) {
+                if (wildcardSupported && mWild && mHost.isEmpty()) {
                     // special case, if no host is provided, but the Authority is wildcard, match
                     return MATCH_CATEGORY_HOST;
                 } else {
diff --git a/core/java/android/content/pm/RegisteredServicesCache.java b/core/java/android/content/pm/RegisteredServicesCache.java
index 7696cbe..78984bd 100644
--- a/core/java/android/content/pm/RegisteredServicesCache.java
+++ b/core/java/android/content/pm/RegisteredServicesCache.java
@@ -174,7 +174,7 @@
 
         // Register for user-related events
         IntentFilter userFilter = new IntentFilter();
-        sdFilter.addAction(Intent.ACTION_USER_REMOVED);
+        userFilter.addAction(Intent.ACTION_USER_REMOVED);
         mContext.registerReceiver(mUserRemovedReceiver, userFilter, null, handler);
     }
 
diff --git a/core/java/android/content/pm/TEST_MAPPING b/core/java/android/content/pm/TEST_MAPPING
index 77b8be3..d04c97c 100644
--- a/core/java/android/content/pm/TEST_MAPPING
+++ b/core/java/android/content/pm/TEST_MAPPING
@@ -37,6 +37,9 @@
       "name": "CarrierAppIntegrationTestCases"
     },
     {
+      "name": "ApkVerityTest"
+    },
+    {
       "name": "CtsIncrementalInstallHostTestCases",
       "options": [
         {
@@ -135,6 +138,9 @@
           "include-filter": "android.appsecurity.cts.AppSecurityTests#testPermissionDiffCert"
         }
       ]
+    },
+    {
+      "name": "CtsInstallHostTestCases"
     }
   ]
 }
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index afaa085..93573d1 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -523,6 +523,7 @@
     private Handler mHandler;
     private boolean mImeSurfaceScheduledForRemoval;
     private ImsConfigurationTracker mConfigTracker = new ImsConfigurationTracker();
+    private boolean mDestroyed;
 
     /**
      * An opaque {@link Binder} token of window requesting {@link InputMethodImpl#showSoftInput}
@@ -599,6 +600,11 @@
         @Override
         public final void initializeInternal(@NonNull IBinder token,
                 IInputMethodPrivilegedOperations privilegedOperations, int configChanges) {
+            if (mDestroyed) {
+                Log.i(TAG, "The InputMethodService has already onDestroyed()."
+                    + "Ignore the initialization.");
+                return;
+            }
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.initializeInternal");
             mConfigTracker.onInitialize(configChanges);
             mPrivOps.set(privilegedOperations);
@@ -1435,6 +1441,7 @@
     }
 
     @Override public void onDestroy() {
+        mDestroyed = true;
         super.onDestroy();
         mRootView.getViewTreeObserver().removeOnComputeInternalInsetsListener(
                 mInsetsComputer);
@@ -1811,15 +1818,19 @@
     
     void updateExtractFrameVisibility() {
         final int vis;
+        updateCandidatesVisibility(mCandidatesVisibility == View.VISIBLE);
+
         if (isFullscreenMode()) {
             vis = mExtractViewHidden ? View.INVISIBLE : View.VISIBLE;
             // "vis" should be applied for the extract frame as well in the fullscreen mode.
             mExtractFrame.setVisibility(vis);
         } else {
-            vis = View.VISIBLE;
+            // mFullscreenArea visibility will according the candidate frame visibility once the
+            // extract frame is gone.
+            vis = mCandidatesVisibility;
             mExtractFrame.setVisibility(View.GONE);
         }
-        updateCandidatesVisibility(mCandidatesVisibility == View.VISIBLE);
+
         if (mDecorViewWasVisible && mFullscreenArea.getVisibility() != vis) {
             int animRes = mThemeAttrs.getResourceId(vis == View.VISIBLE
                     ? com.android.internal.R.styleable.InputMethodService_imeExtractEnterAnimation
diff --git a/core/java/android/net/Ikev2VpnProfile.java b/core/java/android/net/Ikev2VpnProfile.java
index 1b5ab05..5554137 100644
--- a/core/java/android/net/Ikev2VpnProfile.java
+++ b/core/java/android/net/Ikev2VpnProfile.java
@@ -916,9 +916,23 @@
         }
 
         /**
-         *  Sets whether the local traffic is exempted from the VPN.
+         * Sets whether the local traffic is exempted from the VPN.
          *
-         *  @hide TODO(184750836): unhide once the implementation is completed
+         * When this is set, the system will not use the VPN network when an app
+         * tries to send traffic for an IP address that is on a local network.
+         *
+         * Note that there are important security implications. In particular, the
+         * networks that the device connects to typically decides what IP addresses
+         * are part of the local network. This means that for VPNs setting this
+         * flag, it is possible for anybody to set up a public network in such a
+         * way that traffic to arbitrary IP addresses will bypass the VPN, including
+         * traffic to services like DNS. When using this API, please consider the
+         * security implications for your particular case.
+         *
+         * Note that because the local traffic will always bypass the VPN,
+         * it is not possible to set this flag on a non-bypassable VPN.
+         *
+         * @hide TODO(184750836): unhide once the implementation is completed
          */
         @NonNull
         @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS)
diff --git a/core/java/android/net/InternalNetworkUpdateRequest.java b/core/java/android/net/InternalNetworkUpdateRequest.java
index 6f09383..f42c4b7 100644
--- a/core/java/android/net/InternalNetworkUpdateRequest.java
+++ b/core/java/android/net/InternalNetworkUpdateRequest.java
@@ -17,7 +17,6 @@
 package android.net;
 
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -27,7 +26,7 @@
 public final class InternalNetworkUpdateRequest implements Parcelable {
     @NonNull
     private final StaticIpConfiguration mIpConfig;
-    @Nullable
+    @NonNull
     private final NetworkCapabilities mNetworkCapabilities;
 
     @NonNull
@@ -37,20 +36,16 @@
 
     @NonNull
     public NetworkCapabilities getNetworkCapabilities() {
-        return mNetworkCapabilities == null
-                ? null : new NetworkCapabilities(mNetworkCapabilities);
+        return new NetworkCapabilities(mNetworkCapabilities);
     }
 
     /** @hide */
     public InternalNetworkUpdateRequest(@NonNull final StaticIpConfiguration ipConfig,
-            @Nullable final NetworkCapabilities networkCapabilities) {
+            @NonNull final NetworkCapabilities networkCapabilities) {
         Objects.requireNonNull(ipConfig);
+        Objects.requireNonNull(networkCapabilities);
         mIpConfig = new StaticIpConfiguration(ipConfig);
-        if (null == networkCapabilities) {
-            mNetworkCapabilities = null;
-        } else {
-            mNetworkCapabilities = new NetworkCapabilities(networkCapabilities);
-        }
+        mNetworkCapabilities = new NetworkCapabilities(networkCapabilities);
     }
 
     private InternalNetworkUpdateRequest(@NonNull final Parcel source) {
diff --git a/core/java/android/net/VpnService.java b/core/java/android/net/VpnService.java
index 2ced056..1ae1b05 100644
--- a/core/java/android/net/VpnService.java
+++ b/core/java/android/net/VpnService.java
@@ -41,6 +41,7 @@
 import android.os.ServiceManager;
 import android.os.UserHandle;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.net.NetworkUtilsInternal;
 import com.android.internal.net.VpnConfig;
 
@@ -50,6 +51,7 @@
 import java.net.InetAddress;
 import java.net.Socket;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.Set;
 
@@ -471,6 +473,13 @@
         }
     }
 
+    private static void checkNonPrefixBytes(@NonNull InetAddress address, int prefixLength) {
+        final IpPrefix prefix = new IpPrefix(address, prefixLength);
+        if (!prefix.getAddress().equals(address)) {
+            throw new IllegalArgumentException("Bad address");
+        }
+    }
+
     /**
      * Helper class to create a VPN interface. This class should be always
      * used within the scope of the outer {@link VpnService}.
@@ -481,9 +490,9 @@
 
         private final VpnConfig mConfig = new VpnConfig();
         @UnsupportedAppUsage
-        private final List<LinkAddress> mAddresses = new ArrayList<LinkAddress>();
+        private final List<LinkAddress> mAddresses = new ArrayList<>();
         @UnsupportedAppUsage
-        private final List<RouteInfo> mRoutes = new ArrayList<RouteInfo>();
+        private final List<RouteInfo> mRoutes = new ArrayList<>();
 
         public Builder() {
             mConfig.user = VpnService.this.getClass().getName();
@@ -555,7 +564,6 @@
                 throw new IllegalArgumentException("Bad address");
             }
             mAddresses.add(new LinkAddress(address, prefixLength));
-            mConfig.updateAllowedFamilies(address);
             return this;
         }
 
@@ -579,28 +587,68 @@
          * Add a network route to the VPN interface. Both IPv4 and IPv6
          * routes are supported.
          *
+         * If a route with the same destination is already present, its type will be updated.
+         *
+         * @throws IllegalArgumentException if the route is invalid.
+         */
+        @NonNull
+        private Builder addRoute(@NonNull IpPrefix prefix, int type) {
+            check(prefix.getAddress(), prefix.getPrefixLength());
+
+            final RouteInfo newRoute = new RouteInfo(prefix, /* gateway */
+                    null, /* interface */ null, type);
+
+            final int index = findRouteIndexByDestination(newRoute);
+
+            if (index == -1) {
+                mRoutes.add(newRoute);
+            } else {
+                mRoutes.set(index, newRoute);
+            }
+
+            return this;
+        }
+
+        /**
+         * Add a network route to the VPN interface. Both IPv4 and IPv6
+         * routes are supported.
+         *
          * Adding a route implicitly allows traffic from that address family
          * (i.e., IPv4 or IPv6) to be routed over the VPN. @see #allowFamily
          *
+         * Calling this method overrides previous calls to {@link #excludeRoute} for the same
+         * destination.
+         *
+         * If multiple routes match the packet destination, route with the longest prefix takes
+         * precedence.
+         *
          * @throws IllegalArgumentException if the route is invalid.
          */
         @NonNull
         public Builder addRoute(@NonNull InetAddress address, int prefixLength) {
-            check(address, prefixLength);
+            checkNonPrefixBytes(address, prefixLength);
 
-            int offset = prefixLength / 8;
-            byte[] bytes = address.getAddress();
-            if (offset < bytes.length) {
-                for (bytes[offset] <<= prefixLength % 8; offset < bytes.length; ++offset) {
-                    if (bytes[offset] != 0) {
-                        throw new IllegalArgumentException("Bad address");
-                    }
-                }
-            }
-            mRoutes.add(new RouteInfo(new IpPrefix(address, prefixLength), null, null,
-                RouteInfo.RTN_UNICAST));
-            mConfig.updateAllowedFamilies(address);
-            return this;
+            return addRoute(new IpPrefix(address, prefixLength), RouteInfo.RTN_UNICAST);
+        }
+
+        /**
+         * Add a network route to the VPN interface. Both IPv4 and IPv6
+         * routes are supported.
+         *
+         * Adding a route implicitly allows traffic from that address family
+         * (i.e., IPv4 or IPv6) to be routed over the VPN. @see #allowFamily
+         *
+         * Calling this method overrides previous calls to {@link #excludeRoute} for the same
+         * destination.
+         *
+         * If multiple routes match the packet destination, route with the longest prefix takes
+         * precedence.
+         *
+         * @throws IllegalArgumentException if the route is invalid.
+         */
+        @NonNull
+        public Builder addRoute(@NonNull IpPrefix prefix) {
+            return addRoute(prefix, RouteInfo.RTN_UNICAST);
         }
 
         /**
@@ -611,6 +659,12 @@
          * Adding a route implicitly allows traffic from that address family
          * (i.e., IPv4 or IPv6) to be routed over the VPN. @see #allowFamily
          *
+         * Calling this method overrides previous calls to {@link #excludeRoute} for the same
+         * destination.
+         *
+         * If multiple routes match the packet destination, route with the longest prefix takes
+         * precedence.
+         *
          * @throws IllegalArgumentException if the route is invalid.
          * @see #addRoute(InetAddress, int)
          */
@@ -620,6 +674,23 @@
         }
 
         /**
+         * Exclude a network route from the VPN interface. Both IPv4 and IPv6
+         * routes are supported.
+         *
+         * Calling this method overrides previous calls to {@link #addRoute} for the same
+         * destination.
+         *
+         * If multiple routes match the packet destination, route with the longest prefix takes
+         * precedence.
+         *
+         * @throws IllegalArgumentException if the route is invalid.
+         */
+        @NonNull
+        public Builder excludeRoute(@NonNull IpPrefix prefix) {
+            return addRoute(prefix, RouteInfo.RTN_THROW);
+        }
+
+        /**
          * Add a DNS server to the VPN connection. Both IPv4 and IPv6
          * addresses are supported. If none is set, the DNS servers of
          * the default network will be used.
@@ -900,5 +971,23 @@
                 throw new IllegalStateException(e);
             }
         }
+
+        private int findRouteIndexByDestination(RouteInfo route) {
+            for (int i = 0; i < mRoutes.size(); i++) {
+                if (mRoutes.get(i).getDestination().equals(route.getDestination())) {
+                    return i;
+                }
+            }
+            return -1;
+        }
+
+        /**
+         * Method for testing, to observe mRoutes while builder is being used.
+         * @hide
+         */
+        @VisibleForTesting
+        public List<RouteInfo> routes() {
+            return Collections.unmodifiableList(mRoutes);
+        }
     }
 }
diff --git a/core/java/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java b/core/java/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java
index 1ac3f0a..125b573 100644
--- a/core/java/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java
+++ b/core/java/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java
@@ -15,6 +15,9 @@
  */
 package android.net.vcn;
 
+import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_ANY;
+import static android.net.vcn.VcnUnderlyingNetworkTemplate.getMatchCriteriaString;
+
 import static com.android.internal.annotations.VisibleForTesting.Visibility;
 import static com.android.server.vcn.util.PersistableBundleUtils.INTEGER_DESERIALIZER;
 import static com.android.server.vcn.util.PersistableBundleUtils.INTEGER_SERIALIZER;
@@ -23,8 +26,12 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.net.NetworkCapabilities;
+import android.net.vcn.VcnUnderlyingNetworkTemplate.MatchCriteria;
 import android.os.PersistableBundle;
 import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.util.ArraySet;
 
@@ -37,32 +44,36 @@
 import java.util.Objects;
 import java.util.Set;
 
-// TODO: Add documents
-/** @hide */
+/**
+ * This class represents a configuration for a network template class of underlying cellular
+ * networks.
+ *
+ * <p>See {@link VcnUnderlyingNetworkTemplate}
+ */
 public final class VcnCellUnderlyingNetworkTemplate extends VcnUnderlyingNetworkTemplate {
     private static final String ALLOWED_NETWORK_PLMN_IDS_KEY = "mAllowedNetworkPlmnIds";
     @NonNull private final Set<String> mAllowedNetworkPlmnIds;
     private static final String ALLOWED_SPECIFIC_CARRIER_IDS_KEY = "mAllowedSpecificCarrierIds";
     @NonNull private final Set<Integer> mAllowedSpecificCarrierIds;
 
-    private static final String ALLOW_ROAMING_KEY = "mAllowRoaming";
-    private final boolean mAllowRoaming;
+    private static final String ROAMING_MATCH_KEY = "mRoamingMatchCriteria";
+    private final int mRoamingMatchCriteria;
 
-    private static final String REQUIRE_OPPORTUNISTIC_KEY = "mRequireOpportunistic";
-    private final boolean mRequireOpportunistic;
+    private static final String OPPORTUNISTIC_MATCH_KEY = "mOpportunisticMatchCriteria";
+    private final int mOpportunisticMatchCriteria;
 
     private VcnCellUnderlyingNetworkTemplate(
             int networkQuality,
-            boolean allowMetered,
+            int meteredMatchCriteria,
             Set<String> allowedNetworkPlmnIds,
             Set<Integer> allowedSpecificCarrierIds,
-            boolean allowRoaming,
-            boolean requireOpportunistic) {
-        super(NETWORK_PRIORITY_TYPE_CELL, networkQuality, allowMetered);
+            int roamingMatchCriteria,
+            int opportunisticMatchCriteria) {
+        super(NETWORK_PRIORITY_TYPE_CELL, networkQuality, meteredMatchCriteria);
         mAllowedNetworkPlmnIds = new ArraySet<>(allowedNetworkPlmnIds);
         mAllowedSpecificCarrierIds = new ArraySet<>(allowedSpecificCarrierIds);
-        mAllowRoaming = allowRoaming;
-        mRequireOpportunistic = requireOpportunistic;
+        mRoamingMatchCriteria = roamingMatchCriteria;
+        mOpportunisticMatchCriteria = opportunisticMatchCriteria;
 
         validate();
     }
@@ -72,15 +83,17 @@
     protected void validate() {
         super.validate();
         validatePlmnIds(mAllowedNetworkPlmnIds);
-        Objects.requireNonNull(mAllowedSpecificCarrierIds, "allowedCarrierIds is null");
+        Objects.requireNonNull(mAllowedSpecificCarrierIds, "matchingCarrierIds is null");
+        validateMatchCriteria(mRoamingMatchCriteria, "mRoamingMatchCriteria");
+        validateMatchCriteria(mOpportunisticMatchCriteria, "mOpportunisticMatchCriteria");
     }
 
-    private static void validatePlmnIds(Set<String> allowedNetworkPlmnIds) {
-        Objects.requireNonNull(allowedNetworkPlmnIds, "allowedNetworkPlmnIds is null");
+    private static void validatePlmnIds(Set<String> matchingOperatorPlmnIds) {
+        Objects.requireNonNull(matchingOperatorPlmnIds, "matchingOperatorPlmnIds is null");
 
         // A valid PLMN is a concatenation of MNC and MCC, and thus consists of 5 or 6 decimal
         // digits.
-        for (String id : allowedNetworkPlmnIds) {
+        for (String id : matchingOperatorPlmnIds) {
             if ((id.length() == 5 || id.length() == 6) && id.matches("[0-9]+")) {
                 continue;
             } else {
@@ -97,7 +110,7 @@
         Objects.requireNonNull(in, "PersistableBundle is null");
 
         final int networkQuality = in.getInt(NETWORK_QUALITY_KEY);
-        final boolean allowMetered = in.getBoolean(ALLOW_METERED_KEY);
+        final int meteredMatchCriteria = in.getInt(METERED_MATCH_KEY);
 
         final PersistableBundle plmnIdsBundle =
                 in.getPersistableBundle(ALLOWED_NETWORK_PLMN_IDS_KEY);
@@ -114,16 +127,16 @@
                         PersistableBundleUtils.toList(
                                 specificCarrierIdsBundle, INTEGER_DESERIALIZER));
 
-        final boolean allowRoaming = in.getBoolean(ALLOW_ROAMING_KEY);
-        final boolean requireOpportunistic = in.getBoolean(REQUIRE_OPPORTUNISTIC_KEY);
+        final int roamingMatchCriteria = in.getInt(ROAMING_MATCH_KEY);
+        final int opportunisticMatchCriteria = in.getInt(OPPORTUNISTIC_MATCH_KEY);
 
         return new VcnCellUnderlyingNetworkTemplate(
                 networkQuality,
-                allowMetered,
+                meteredMatchCriteria,
                 allowedNetworkPlmnIds,
                 allowedSpecificCarrierIds,
-                allowRoaming,
-                requireOpportunistic);
+                roamingMatchCriteria,
+                opportunisticMatchCriteria);
     }
 
     /** @hide */
@@ -143,35 +156,51 @@
                         new ArrayList<>(mAllowedSpecificCarrierIds), INTEGER_SERIALIZER);
         result.putPersistableBundle(ALLOWED_SPECIFIC_CARRIER_IDS_KEY, specificCarrierIdsBundle);
 
-        result.putBoolean(ALLOW_ROAMING_KEY, mAllowRoaming);
-        result.putBoolean(REQUIRE_OPPORTUNISTIC_KEY, mRequireOpportunistic);
+        result.putInt(ROAMING_MATCH_KEY, mRoamingMatchCriteria);
+        result.putInt(OPPORTUNISTIC_MATCH_KEY, mOpportunisticMatchCriteria);
 
         return result;
     }
 
-    /** Retrieve the allowed PLMN IDs, or an empty set if any PLMN ID is acceptable. */
+    /**
+     * Retrieve the matching operator PLMN IDs, or an empty set if any PLMN ID is acceptable.
+     *
+     * @see Builder#setOperatorPlmnIds(Set)
+     */
     @NonNull
-    public Set<String> getAllowedOperatorPlmnIds() {
+    public Set<String> getOperatorPlmnIds() {
         return Collections.unmodifiableSet(mAllowedNetworkPlmnIds);
     }
 
     /**
-     * Retrieve the allowed specific carrier IDs, or an empty set if any specific carrier ID is
-     * acceptable.
+     * Retrieve the matching sim specific carrier IDs, or an empty set if any sim specific carrier
+     * ID is acceptable.
+     *
+     * @see Builder#setSimSpecificCarrierIds(Set)
      */
     @NonNull
-    public Set<Integer> getAllowedSpecificCarrierIds() {
+    public Set<Integer> getSimSpecificCarrierIds() {
         return Collections.unmodifiableSet(mAllowedSpecificCarrierIds);
     }
 
-    /** Return if roaming is allowed. */
-    public boolean allowRoaming() {
-        return mAllowRoaming;
+    /**
+     * Return the matching criteria for roaming networks.
+     *
+     * @see Builder#setRoaming(int)
+     */
+    @MatchCriteria
+    public int getRoaming() {
+        return mRoamingMatchCriteria;
     }
 
-    /** Return if requiring an opportunistic network. */
-    public boolean requireOpportunistic() {
-        return mRequireOpportunistic;
+    /**
+     * Return the matching criteria for opportunistic cellular subscriptions.
+     *
+     * @see Builder#setOpportunistic(int)
+     */
+    @MatchCriteria
+    public int getOpportunistic() {
+        return mOpportunisticMatchCriteria;
     }
 
     @Override
@@ -180,8 +209,8 @@
                 super.hashCode(),
                 mAllowedNetworkPlmnIds,
                 mAllowedSpecificCarrierIds,
-                mAllowRoaming,
-                mRequireOpportunistic);
+                mRoamingMatchCriteria,
+                mOpportunisticMatchCriteria);
     }
 
     @Override
@@ -197,8 +226,8 @@
         final VcnCellUnderlyingNetworkTemplate rhs = (VcnCellUnderlyingNetworkTemplate) other;
         return Objects.equals(mAllowedNetworkPlmnIds, rhs.mAllowedNetworkPlmnIds)
                 && Objects.equals(mAllowedSpecificCarrierIds, rhs.mAllowedSpecificCarrierIds)
-                && mAllowRoaming == rhs.mAllowRoaming
-                && mRequireOpportunistic == rhs.mRequireOpportunistic;
+                && mRoamingMatchCriteria == rhs.mRoamingMatchCriteria
+                && mOpportunisticMatchCriteria == rhs.mOpportunisticMatchCriteria;
     }
 
     /** @hide */
@@ -206,77 +235,137 @@
     void dumpTransportSpecificFields(IndentingPrintWriter pw) {
         pw.println("mAllowedNetworkPlmnIds: " + mAllowedNetworkPlmnIds.toString());
         pw.println("mAllowedSpecificCarrierIds: " + mAllowedSpecificCarrierIds.toString());
-        pw.println("mAllowRoaming: " + mAllowRoaming);
-        pw.println("mRequireOpportunistic: " + mRequireOpportunistic);
+        pw.println("mRoamingMatchCriteria: " + getMatchCriteriaString(mRoamingMatchCriteria));
+        pw.println(
+                "mOpportunisticMatchCriteria: "
+                        + getMatchCriteriaString(mOpportunisticMatchCriteria));
     }
 
-    /** This class is used to incrementally build WifiNetworkPriority objects. */
-    public static final class Builder extends VcnUnderlyingNetworkTemplate.Builder<Builder> {
+    /** This class is used to incrementally build VcnCellUnderlyingNetworkTemplate objects. */
+    public static final class Builder {
+        private int mNetworkQuality = NETWORK_QUALITY_ANY;
+        private int mMeteredMatchCriteria = MATCH_ANY;
+
         @NonNull private final Set<String> mAllowedNetworkPlmnIds = new ArraySet<>();
         @NonNull private final Set<Integer> mAllowedSpecificCarrierIds = new ArraySet<>();
 
-        private boolean mAllowRoaming = false;
-        private boolean mRequireOpportunistic = false;
+        private int mRoamingMatchCriteria = MATCH_ANY;
+        private int mOpportunisticMatchCriteria = MATCH_ANY;
 
         /** Construct a Builder object. */
         public Builder() {}
 
         /**
-         * Set allowed operator PLMN IDs.
+         * Set the required network quality to match this template.
+         *
+         * <p>Network quality is a aggregation of multiple signals that reflect the network link
+         * metrics. For example, the network validation bit (see {@link
+         * NetworkCapabilities#NET_CAPABILITY_VALIDATED}), estimated first hop transport bandwidth
+         * and signal strength.
+         *
+         * @param networkQuality the required network quality. Defaults to NETWORK_QUALITY_ANY
+         * @hide
+         */
+        @NonNull
+        public Builder setNetworkQuality(@NetworkQuality int networkQuality) {
+            validateNetworkQuality(networkQuality);
+
+            mNetworkQuality = networkQuality;
+            return this;
+        }
+
+        /**
+         * Set the matching criteria for metered networks.
+         *
+         * <p>A template where setMetered(MATCH_REQUIRED) will only match metered networks (one
+         * without NET_CAPABILITY_NOT_METERED). A template where setMetered(MATCH_FORBIDDEN) will
+         * only match a network that is not metered (one with NET_CAPABILITY_NOT_METERED).
+         *
+         * @param matchCriteria the matching criteria for metered networks. Defaults to {@link
+         *     #MATCH_ANY}.
+         * @see NetworkCapabilities#NET_CAPABILITY_NOT_METERED
+         */
+        // The matching getter is defined in the super class. Please see {@link
+        // VcnUnderlyingNetworkTemplate#getMetered()}
+        @SuppressLint("MissingGetterMatchingBuilder")
+        @NonNull
+        public Builder setMetered(@MatchCriteria int matchCriteria) {
+            validateMatchCriteria(matchCriteria, "setMetered");
+
+            mMeteredMatchCriteria = matchCriteria;
+            return this;
+        }
+
+        /**
+         * Set operator PLMN IDs with which a network can match this template.
          *
          * <p>This is used to distinguish cases where roaming agreements may dictate a different
          * priority from a partner's networks.
          *
-         * @param allowedNetworkPlmnIds the allowed operator PLMN IDs in String. Defaults to an
-         *     empty set, allowing ANY PLMN ID. A valid PLMN is a concatenation of MNC and MCC, and
-         *     thus consists of 5 or 6 decimal digits. See {@link SubscriptionInfo#getMccString()}
-         *     and {@link SubscriptionInfo#getMncString()}.
+         * @param operatorPlmnIds the matching operator PLMN IDs in String. Network with one of the
+         *     matching PLMN IDs can match this template. If the set is empty, any PLMN ID will
+         *     match. The default is an empty set. A valid PLMN is a concatenation of MNC and MCC,
+         *     and thus consists of 5 or 6 decimal digits.
+         * @see SubscriptionInfo#getMccString()
+         * @see SubscriptionInfo#getMncString()
          */
         @NonNull
-        public Builder setAllowedOperatorPlmnIds(@NonNull Set<String> allowedNetworkPlmnIds) {
-            validatePlmnIds(allowedNetworkPlmnIds);
+        public Builder setOperatorPlmnIds(@NonNull Set<String> operatorPlmnIds) {
+            validatePlmnIds(operatorPlmnIds);
 
             mAllowedNetworkPlmnIds.clear();
-            mAllowedNetworkPlmnIds.addAll(allowedNetworkPlmnIds);
+            mAllowedNetworkPlmnIds.addAll(operatorPlmnIds);
             return this;
         }
 
         /**
-         * Set allowed specific carrier IDs.
+         * Set sim specific carrier IDs with which a network can match this template.
          *
-         * @param allowedSpecificCarrierIds the allowed specific carrier IDs. Defaults to an empty
-         *     set, allowing ANY carrier ID. See {@link TelephonyManager#getSimSpecificCarrierId()}.
+         * @param simSpecificCarrierIds the matching sim specific carrier IDs. Network with one of
+         *     the sim specific carrier IDs can match this template. If the set is empty, any
+         *     carrier ID will match. The default is an empty set.
+         * @see TelephonyManager#getSimSpecificCarrierId()
          */
         @NonNull
-        public Builder setAllowedSpecificCarrierIds(
-                @NonNull Set<Integer> allowedSpecificCarrierIds) {
-            Objects.requireNonNull(allowedSpecificCarrierIds, "allowedCarrierIds is null");
+        public Builder setSimSpecificCarrierIds(@NonNull Set<Integer> simSpecificCarrierIds) {
+            Objects.requireNonNull(simSpecificCarrierIds, "simSpecificCarrierIds is null");
+
             mAllowedSpecificCarrierIds.clear();
-            mAllowedSpecificCarrierIds.addAll(allowedSpecificCarrierIds);
+            mAllowedSpecificCarrierIds.addAll(simSpecificCarrierIds);
             return this;
         }
 
         /**
-         * Set if roaming is allowed.
+         * Set the matching criteria for roaming networks.
          *
-         * @param allowRoaming the flag to indicate if roaming is allowed. Defaults to {@code
-         *     false}.
+         * <p>A template where setRoaming(MATCH_REQUIRED) will only match roaming networks (one
+         * without NET_CAPABILITY_NOT_ROAMING). A template where setRoaming(MATCH_FORBIDDEN) will
+         * only match a network that is not roaming (one with NET_CAPABILITY_NOT_ROAMING).
+         *
+         * @param matchCriteria the matching criteria for roaming networks. Defaults to {@link
+         *     #MATCH_ANY}.
+         * @see NetworkCapabilities#NET_CAPABILITY_NOT_ROAMING
          */
         @NonNull
-        public Builder setAllowRoaming(boolean allowRoaming) {
-            mAllowRoaming = allowRoaming;
+        public Builder setRoaming(@MatchCriteria int matchCriteria) {
+            validateMatchCriteria(matchCriteria, "setRoaming");
+
+            mRoamingMatchCriteria = matchCriteria;
             return this;
         }
 
         /**
-         * Set if requiring an opportunistic network.
+         * Set the matching criteria for opportunistic cellular subscriptions.
          *
-         * @param requireOpportunistic the flag to indicate if caller requires an opportunistic
-         *     network. Defaults to {@code false}.
+         * @param matchCriteria the matching criteria for opportunistic cellular subscriptions.
+         *     Defaults to {@link #MATCH_ANY}.
+         * @see SubscriptionManager#setOpportunistic(boolean, int)
          */
         @NonNull
-        public Builder setRequireOpportunistic(boolean requireOpportunistic) {
-            mRequireOpportunistic = requireOpportunistic;
+        public Builder setOpportunistic(@MatchCriteria int matchCriteria) {
+            validateMatchCriteria(matchCriteria, "setOpportunistic");
+
+            mOpportunisticMatchCriteria = matchCriteria;
             return this;
         }
 
@@ -285,17 +374,11 @@
         public VcnCellUnderlyingNetworkTemplate build() {
             return new VcnCellUnderlyingNetworkTemplate(
                     mNetworkQuality,
-                    mAllowMetered,
+                    mMeteredMatchCriteria,
                     mAllowedNetworkPlmnIds,
                     mAllowedSpecificCarrierIds,
-                    mAllowRoaming,
-                    mRequireOpportunistic);
-        }
-
-        /** @hide */
-        @Override
-        Builder self() {
-            return this;
+                    mRoamingMatchCriteria,
+                    mOpportunisticMatchCriteria);
         }
     }
 }
diff --git a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
index d07c24a..92956e8 100644
--- a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
+++ b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
@@ -16,6 +16,7 @@
 package android.net.vcn;
 
 import static android.net.ipsec.ike.IkeSessionParams.IKE_OPTION_MOBIKE;
+import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_REQUIRED;
 import static android.net.vcn.VcnUnderlyingNetworkTemplate.NETWORK_QUALITY_OK;
 
 import static com.android.internal.annotations.VisibleForTesting.Visibility;
@@ -42,7 +43,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.LinkedHashSet;
+import java.util.List;
 import java.util.Objects;
 import java.util.Set;
 import java.util.SortedSet;
@@ -162,30 +163,24 @@
 
     /** @hide */
     @VisibleForTesting(visibility = Visibility.PRIVATE)
-    public static final LinkedHashSet<VcnUnderlyingNetworkTemplate>
-            DEFAULT_UNDERLYING_NETWORK_PRIORITIES = new LinkedHashSet<>();
+    public static final List<VcnUnderlyingNetworkTemplate> DEFAULT_UNDERLYING_NETWORK_TEMPLATES =
+            new ArrayList<>();
 
     static {
-        DEFAULT_UNDERLYING_NETWORK_PRIORITIES.add(
+        DEFAULT_UNDERLYING_NETWORK_TEMPLATES.add(
                 new VcnCellUnderlyingNetworkTemplate.Builder()
                         .setNetworkQuality(NETWORK_QUALITY_OK)
-                        .setAllowMetered(true /* allowMetered */)
-                        .setAllowRoaming(true /* allowRoaming */)
-                        .setRequireOpportunistic(true /* requireOpportunistic */)
+                        .setOpportunistic(MATCH_REQUIRED)
                         .build());
 
-        DEFAULT_UNDERLYING_NETWORK_PRIORITIES.add(
+        DEFAULT_UNDERLYING_NETWORK_TEMPLATES.add(
                 new VcnWifiUnderlyingNetworkTemplate.Builder()
                         .setNetworkQuality(NETWORK_QUALITY_OK)
-                        .setAllowMetered(true /* allowMetered */)
                         .build());
 
-        DEFAULT_UNDERLYING_NETWORK_PRIORITIES.add(
+        DEFAULT_UNDERLYING_NETWORK_TEMPLATES.add(
                 new VcnCellUnderlyingNetworkTemplate.Builder()
                         .setNetworkQuality(NETWORK_QUALITY_OK)
-                        .setAllowMetered(true /* allowMetered */)
-                        .setAllowRoaming(true /* allowRoaming */)
-                        .setRequireOpportunistic(false /* requireOpportunistic */)
                         .build());
     }
 
@@ -200,9 +195,9 @@
 
     /** @hide */
     @VisibleForTesting(visibility = Visibility.PRIVATE)
-    public static final String UNDERLYING_NETWORK_PRIORITIES_KEY = "mUnderlyingNetworkPriorities";
+    public static final String UNDERLYING_NETWORK_TEMPLATES_KEY = "mUnderlyingNetworkTemplates";
 
-    @NonNull private final LinkedHashSet<VcnUnderlyingNetworkTemplate> mUnderlyingNetworkPriorities;
+    @NonNull private final List<VcnUnderlyingNetworkTemplate> mUnderlyingNetworkTemplates;
 
     private static final String MAX_MTU_KEY = "mMaxMtu";
     private final int mMaxMtu;
@@ -215,7 +210,7 @@
             @NonNull String gatewayConnectionName,
             @NonNull IkeTunnelConnectionParams tunnelConnectionParams,
             @NonNull Set<Integer> exposedCapabilities,
-            @NonNull LinkedHashSet<VcnUnderlyingNetworkTemplate> underlyingNetworkPriorities,
+            @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
             @NonNull long[] retryIntervalsMs,
             @IntRange(from = MIN_MTU_V6) int maxMtu) {
         mGatewayConnectionName = gatewayConnectionName;
@@ -224,9 +219,9 @@
         mRetryIntervalsMs = retryIntervalsMs;
         mMaxMtu = maxMtu;
 
-        mUnderlyingNetworkPriorities = new LinkedHashSet<>(underlyingNetworkPriorities);
-        if (mUnderlyingNetworkPriorities.isEmpty()) {
-            mUnderlyingNetworkPriorities.addAll(DEFAULT_UNDERLYING_NETWORK_PRIORITIES);
+        mUnderlyingNetworkTemplates = new ArrayList<>(underlyingNetworkTemplates);
+        if (mUnderlyingNetworkTemplates.isEmpty()) {
+            mUnderlyingNetworkTemplates.addAll(DEFAULT_UNDERLYING_NETWORK_TEMPLATES);
         }
 
         validate();
@@ -250,22 +245,19 @@
         mExposedCapabilities = new TreeSet<>(PersistableBundleUtils.toList(
                 exposedCapsBundle, PersistableBundleUtils.INTEGER_DESERIALIZER));
 
-        final PersistableBundle networkPrioritiesBundle =
-                in.getPersistableBundle(UNDERLYING_NETWORK_PRIORITIES_KEY);
+        final PersistableBundle networkTemplatesBundle =
+                in.getPersistableBundle(UNDERLYING_NETWORK_TEMPLATES_KEY);
 
-        if (networkPrioritiesBundle == null) {
-            // UNDERLYING_NETWORK_PRIORITIES_KEY was added in Android T. Thus
+        if (networkTemplatesBundle == null) {
+            // UNDERLYING_NETWORK_TEMPLATES_KEY was added in Android T. Thus
             // VcnGatewayConnectionConfig created on old platforms will not have this data and will
             // be assigned with the default value
-            mUnderlyingNetworkPriorities =
-                    new LinkedHashSet<>(DEFAULT_UNDERLYING_NETWORK_PRIORITIES);
-
+            mUnderlyingNetworkTemplates = new ArrayList<>(DEFAULT_UNDERLYING_NETWORK_TEMPLATES);
         } else {
-            mUnderlyingNetworkPriorities =
-                    new LinkedHashSet<>(
-                            PersistableBundleUtils.toList(
-                                    networkPrioritiesBundle,
-                                    VcnUnderlyingNetworkTemplate::fromPersistableBundle));
+            mUnderlyingNetworkTemplates =
+                    PersistableBundleUtils.toList(
+                            networkTemplatesBundle,
+                            VcnUnderlyingNetworkTemplate::fromPersistableBundle);
         }
 
         mRetryIntervalsMs = in.getLongArray(RETRY_INTERVAL_MS_KEY);
@@ -285,7 +277,7 @@
             checkValidCapability(cap);
         }
 
-        Objects.requireNonNull(mUnderlyingNetworkPriorities, "underlyingNetworkPriorities is null");
+        validateNetworkTemplateList(mUnderlyingNetworkTemplates);
         Objects.requireNonNull(mRetryIntervalsMs, "retryIntervalsMs was null");
         validateRetryInterval(mRetryIntervalsMs);
 
@@ -314,6 +306,19 @@
         }
     }
 
+    private static void validateNetworkTemplateList(
+            List<VcnUnderlyingNetworkTemplate> networkPriorityRules) {
+        Objects.requireNonNull(networkPriorityRules, "networkPriorityRules is null");
+
+        Set<VcnUnderlyingNetworkTemplate> existingRules = new ArraySet<>();
+        for (VcnUnderlyingNetworkTemplate rule : networkPriorityRules) {
+            Objects.requireNonNull(rule, "Found null value VcnUnderlyingNetworkTemplate");
+            if (!existingRules.add(rule)) {
+                throw new IllegalArgumentException("Found duplicate VcnUnderlyingNetworkTemplate");
+            }
+        }
+    }
+
     /**
      * Returns the configured Gateway Connection name.
      *
@@ -368,15 +373,13 @@
     }
 
     /**
-     * Retrieve the configured VcnUnderlyingNetworkTemplate list, or a default list if it is not
-     * configured.
+     * Retrieve the VcnUnderlyingNetworkTemplate list, or a default list if it is not configured.
      *
-     * @see Builder#setVcnUnderlyingNetworkPriorities(LinkedHashSet<VcnUnderlyingNetworkTemplate>)
-     * @hide
+     * @see Builder#setVcnUnderlyingNetworkPriorities(List)
      */
     @NonNull
-    public LinkedHashSet<VcnUnderlyingNetworkTemplate> getVcnUnderlyingNetworkPriorities() {
-        return new LinkedHashSet<>(mUnderlyingNetworkPriorities);
+    public List<VcnUnderlyingNetworkTemplate> getVcnUnderlyingNetworkPriorities() {
+        return new ArrayList<>(mUnderlyingNetworkTemplates);
     }
 
     /**
@@ -415,15 +418,15 @@
                 PersistableBundleUtils.fromList(
                         new ArrayList<>(mExposedCapabilities),
                         PersistableBundleUtils.INTEGER_SERIALIZER);
-        final PersistableBundle networkPrioritiesBundle =
+        final PersistableBundle networkTemplatesBundle =
                 PersistableBundleUtils.fromList(
-                        new ArrayList<>(mUnderlyingNetworkPriorities),
+                        mUnderlyingNetworkTemplates,
                         VcnUnderlyingNetworkTemplate::toPersistableBundle);
 
         result.putString(GATEWAY_CONNECTION_NAME_KEY, mGatewayConnectionName);
         result.putPersistableBundle(TUNNEL_CONNECTION_PARAMS_KEY, tunnelConnectionParamsBundle);
         result.putPersistableBundle(EXPOSED_CAPABILITIES_KEY, exposedCapsBundle);
-        result.putPersistableBundle(UNDERLYING_NETWORK_PRIORITIES_KEY, networkPrioritiesBundle);
+        result.putPersistableBundle(UNDERLYING_NETWORK_TEMPLATES_KEY, networkTemplatesBundle);
         result.putLongArray(RETRY_INTERVAL_MS_KEY, mRetryIntervalsMs);
         result.putInt(MAX_MTU_KEY, mMaxMtu);
 
@@ -436,7 +439,7 @@
                 mGatewayConnectionName,
                 mTunnelConnectionParams,
                 mExposedCapabilities,
-                mUnderlyingNetworkPriorities,
+                mUnderlyingNetworkTemplates,
                 Arrays.hashCode(mRetryIntervalsMs),
                 mMaxMtu);
     }
@@ -451,7 +454,7 @@
         return mGatewayConnectionName.equals(rhs.mGatewayConnectionName)
                 && mTunnelConnectionParams.equals(rhs.mTunnelConnectionParams)
                 && mExposedCapabilities.equals(rhs.mExposedCapabilities)
-                && mUnderlyingNetworkPriorities.equals(rhs.mUnderlyingNetworkPriorities)
+                && mUnderlyingNetworkTemplates.equals(rhs.mUnderlyingNetworkTemplates)
                 && Arrays.equals(mRetryIntervalsMs, rhs.mRetryIntervalsMs)
                 && mMaxMtu == rhs.mMaxMtu;
     }
@@ -465,8 +468,8 @@
         @NonNull private final Set<Integer> mExposedCapabilities = new ArraySet();
 
         @NonNull
-        private final LinkedHashSet<VcnUnderlyingNetworkTemplate> mUnderlyingNetworkPriorities =
-                new LinkedHashSet<>(DEFAULT_UNDERLYING_NETWORK_PRIORITIES);
+        private final List<VcnUnderlyingNetworkTemplate> mUnderlyingNetworkTemplates =
+                new ArrayList<>(DEFAULT_UNDERLYING_NETWORK_TEMPLATES);
 
         @NonNull private long[] mRetryIntervalsMs = DEFAULT_RETRY_INTERVALS_MS;
         private int mMaxMtu = DEFAULT_MAX_MTU;
@@ -539,27 +542,37 @@
         }
 
         /**
-         * Set the VcnUnderlyingNetworkTemplate list.
+         * Set the list of templates to match underlying networks against, in high-to-low priority
+         * order.
          *
-         * @param underlyingNetworkPriorities a list of unique VcnUnderlyingNetworkPriorities that
-         *     are ordered from most to least preferred, or an empty list to use the default
-         *     prioritization. The default network prioritization is Opportunistic cellular, Carrier
-         *     WiFi and Macro cellular
-         * @return
+         * <p>To select the VCN underlying network, the VCN connection will go through all the
+         * network candidates and return a network matching the highest priority rule.
+         *
+         * <p>If multiple networks match the same rule, the VCN will prefer an already-selected
+         * network as opposed to a new/unselected network. However, if both are new/unselected
+         * networks, a network will be chosen arbitrarily amongst the networks matching the highest
+         * priority rule.
+         *
+         * <p>If all networks fail to match the rules provided, an underlying network will still be
+         * selected (at random if necessary).
+         *
+         * @param underlyingNetworkTemplates a list of unique VcnUnderlyingNetworkTemplates that are
+         *     ordered from most to least preferred, or an empty list to use the default
+         *     prioritization. The default network prioritization order is Opportunistic cellular,
+         *     Carrier WiFi and then Macro cellular.
+         * @return this {@link Builder} instance, for chaining
          */
-        /** @hide */
         @NonNull
         public Builder setVcnUnderlyingNetworkPriorities(
-                @NonNull LinkedHashSet<VcnUnderlyingNetworkTemplate> underlyingNetworkPriorities) {
-            Objects.requireNonNull(
-                    mUnderlyingNetworkPriorities, "underlyingNetworkPriorities is null");
+                @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates) {
+            validateNetworkTemplateList(underlyingNetworkTemplates);
 
-            mUnderlyingNetworkPriorities.clear();
+            mUnderlyingNetworkTemplates.clear();
 
-            if (underlyingNetworkPriorities.isEmpty()) {
-                mUnderlyingNetworkPriorities.addAll(DEFAULT_UNDERLYING_NETWORK_PRIORITIES);
+            if (underlyingNetworkTemplates.isEmpty()) {
+                mUnderlyingNetworkTemplates.addAll(DEFAULT_UNDERLYING_NETWORK_TEMPLATES);
             } else {
-                mUnderlyingNetworkPriorities.addAll(underlyingNetworkPriorities);
+                mUnderlyingNetworkTemplates.addAll(underlyingNetworkTemplates);
             }
 
             return this;
@@ -629,7 +642,7 @@
                     mGatewayConnectionName,
                     mTunnelConnectionParams,
                     mExposedCapabilities,
-                    mUnderlyingNetworkPriorities,
+                    mUnderlyingNetworkTemplates,
                     mRetryIntervalsMs,
                     mMaxMtu);
         }
diff --git a/core/java/android/net/vcn/VcnUnderlyingNetworkTemplate.java b/core/java/android/net/vcn/VcnUnderlyingNetworkTemplate.java
index d306d5c..60fc936 100644
--- a/core/java/android/net/vcn/VcnUnderlyingNetworkTemplate.java
+++ b/core/java/android/net/vcn/VcnUnderlyingNetworkTemplate.java
@@ -15,6 +15,8 @@
  */
 package android.net.vcn;
 
+import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_ANY;
+
 import static com.android.internal.annotations.VisibleForTesting.Visibility;
 
 import android.annotation.IntDef;
@@ -31,17 +33,24 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.Objects;
 
-// TODO: Add documents
-/** @hide */
+/**
+ * This class represents a template containing set of underlying network requirements for doing
+ * route selection.
+ *
+ * <p>Apps provisioning a VCN can configure the underlying network priority for each Gateway
+ * Connection by setting a list (in priority order, most to least preferred) of the appropriate
+ * subclasses in the VcnGatewayConnectionConfig. See {@link
+ * VcnGatewayConnectionConfig.Builder#setVcnUnderlyingNetworkPriorities}
+ */
 public abstract class VcnUnderlyingNetworkTemplate {
     /** @hide */
-    protected static final int NETWORK_PRIORITY_TYPE_WIFI = 1;
+    static final int NETWORK_PRIORITY_TYPE_WIFI = 1;
     /** @hide */
-    protected static final int NETWORK_PRIORITY_TYPE_CELL = 2;
+    static final int NETWORK_PRIORITY_TYPE_CELL = 2;
 
-    /** Denotes that any network quality is acceptable */
+    /** Denotes that any network quality is acceptable. @hide */
     public static final int NETWORK_QUALITY_ANY = 0;
-    /** Denotes that network quality needs to be OK */
+    /** Denotes that network quality needs to be OK. @hide */
     public static final int NETWORK_QUALITY_OK = 100000;
 
     private static final SparseArray<String> NETWORK_QUALITY_TO_STRING_MAP = new SparseArray<>();
@@ -56,34 +65,82 @@
     @IntDef({NETWORK_QUALITY_OK, NETWORK_QUALITY_ANY})
     public @interface NetworkQuality {}
 
+    /**
+     * Used to configure the matching criteria of a network characteristic. This may include network
+     * capabilities, or cellular subscription information. Denotes that networks with or without the
+     * characteristic are both acceptable to match this template.
+     */
+    public static final int MATCH_ANY = 0;
+
+    /**
+     * Used to configure the matching criteria of a network characteristic. This may include network
+     * capabilities, or cellular subscription information. Denotes that a network MUST have the
+     * capability in order to match this template.
+     */
+    public static final int MATCH_REQUIRED = 1;
+
+    /**
+     * Used to configure the matching criteria of a network characteristic. This may include network
+     * capabilities, or cellular subscription information. Denotes that a network MUST NOT have the
+     * capability in order to match this template.
+     */
+    public static final int MATCH_FORBIDDEN = 2;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({MATCH_ANY, MATCH_REQUIRED, MATCH_FORBIDDEN})
+    public @interface MatchCriteria {}
+
+    private static final SparseArray<String> MATCH_CRITERIA_TO_STRING_MAP = new SparseArray<>();
+
+    static {
+        MATCH_CRITERIA_TO_STRING_MAP.put(MATCH_ANY, "MATCH_ANY");
+        MATCH_CRITERIA_TO_STRING_MAP.put(MATCH_REQUIRED, "MATCH_REQUIRED");
+        MATCH_CRITERIA_TO_STRING_MAP.put(MATCH_FORBIDDEN, "MATCH_FORBIDDEN");
+    }
+
     private static final String NETWORK_PRIORITY_TYPE_KEY = "mNetworkPriorityType";
     private final int mNetworkPriorityType;
 
     /** @hide */
-    protected static final String NETWORK_QUALITY_KEY = "mNetworkQuality";
+    static final String NETWORK_QUALITY_KEY = "mNetworkQuality";
+
     private final int mNetworkQuality;
 
     /** @hide */
-    protected static final String ALLOW_METERED_KEY = "mAllowMetered";
-    private final boolean mAllowMetered;
+    static final String METERED_MATCH_KEY = "mMeteredMatchCriteria";
+
+    private final int mMeteredMatchCriteria;
 
     /** @hide */
-    protected VcnUnderlyingNetworkTemplate(
-            int networkPriorityType, int networkQuality, boolean allowMetered) {
+    VcnUnderlyingNetworkTemplate(
+            int networkPriorityType, int networkQuality, int meteredMatchCriteria) {
         mNetworkPriorityType = networkPriorityType;
         mNetworkQuality = networkQuality;
-        mAllowMetered = allowMetered;
+        mMeteredMatchCriteria = meteredMatchCriteria;
     }
 
-    private static void validateNetworkQuality(int networkQuality) {
+    /** @hide */
+    static void validateNetworkQuality(int networkQuality) {
         Preconditions.checkArgument(
                 networkQuality == NETWORK_QUALITY_ANY || networkQuality == NETWORK_QUALITY_OK,
                 "Invalid networkQuality:" + networkQuality);
     }
 
     /** @hide */
+    static void validateMatchCriteria(int meteredMatchCriteria, String matchingCapability) {
+        Preconditions.checkArgument(
+                MATCH_CRITERIA_TO_STRING_MAP.contains(meteredMatchCriteria),
+                "Invalid matching criteria: "
+                        + meteredMatchCriteria
+                        + " for "
+                        + matchingCapability);
+    }
+
+    /** @hide */
     protected void validate() {
         validateNetworkQuality(mNetworkQuality);
+        validateMatchCriteria(mMeteredMatchCriteria, "mMeteredMatchCriteria");
     }
 
     /** @hide */
@@ -112,14 +169,14 @@
 
         result.putInt(NETWORK_PRIORITY_TYPE_KEY, mNetworkPriorityType);
         result.putInt(NETWORK_QUALITY_KEY, mNetworkQuality);
-        result.putBoolean(ALLOW_METERED_KEY, mAllowMetered);
+        result.putInt(METERED_MATCH_KEY, mMeteredMatchCriteria);
 
         return result;
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mNetworkPriorityType, mNetworkQuality, mAllowMetered);
+        return Objects.hash(mNetworkPriorityType, mNetworkQuality, mMeteredMatchCriteria);
     }
 
     @Override
@@ -131,7 +188,17 @@
         final VcnUnderlyingNetworkTemplate rhs = (VcnUnderlyingNetworkTemplate) other;
         return mNetworkPriorityType == rhs.mNetworkPriorityType
                 && mNetworkQuality == rhs.mNetworkQuality
-                && mAllowMetered == rhs.mAllowMetered;
+                && mMeteredMatchCriteria == rhs.mMeteredMatchCriteria;
+    }
+
+    /** @hide */
+    static String getNameString(SparseArray<String> toStringMap, int key) {
+        return toStringMap.get(key, "Invalid value " + key);
+    }
+
+    /** @hide */
+    static String getMatchCriteriaString(int meteredMatchCriteria) {
+        return getNameString(MATCH_CRITERIA_TO_STRING_MAP, meteredMatchCriteria);
     }
 
     /** @hide */
@@ -148,65 +215,32 @@
 
         pw.println(
                 "mNetworkQuality: "
-                        + NETWORK_QUALITY_TO_STRING_MAP.get(
-                                mNetworkQuality, "Invalid value " + mNetworkQuality));
-        pw.println("mAllowMetered: " + mAllowMetered);
+                        + getNameString(NETWORK_QUALITY_TO_STRING_MAP, mNetworkQuality));
+        pw.println("mMeteredMatchCriteria: " + getMatchCriteriaString(mMeteredMatchCriteria));
         dumpTransportSpecificFields(pw);
 
         pw.decreaseIndent();
     }
 
-    /** Retrieve the required network quality. */
+    /**
+     * Retrieve the required network quality to match this template.
+     *
+     * @see Builder#setNetworkQuality(int)
+     * @hide
+     */
     @NetworkQuality
     public int getNetworkQuality() {
         return mNetworkQuality;
     }
 
-    /** Return if a metered network is allowed. */
-    public boolean allowMetered() {
-        return mAllowMetered;
-    }
-
     /**
-     * This class is used to incrementally build VcnUnderlyingNetworkTemplate objects.
+     * Return the matching criteria for metered networks.
      *
-     * @param <T> The subclass to be built.
+     * @see VcnWifiUnderlyingNetworkTemplate.Builder#setMetered(int)
+     * @see VcnCellUnderlyingNetworkTemplate.Builder#setMetered(int)
      */
-    public abstract static class Builder<T extends Builder<T>> {
-        /** @hide */
-        protected int mNetworkQuality = NETWORK_QUALITY_ANY;
-        /** @hide */
-        protected boolean mAllowMetered = false;
-
-        /** @hide */
-        protected Builder() {}
-
-        /**
-         * Set the required network quality.
-         *
-         * @param networkQuality the required network quality. Defaults to NETWORK_QUALITY_ANY
-         */
-        @NonNull
-        public T setNetworkQuality(@NetworkQuality int networkQuality) {
-            validateNetworkQuality(networkQuality);
-
-            mNetworkQuality = networkQuality;
-            return self();
-        }
-
-        /**
-         * Set if a metered network is allowed.
-         *
-         * @param allowMetered the flag to indicate if a metered network is allowed, defaults to
-         *     {@code false}
-         */
-        @NonNull
-        public T setAllowMetered(boolean allowMetered) {
-            mAllowMetered = allowMetered;
-            return self();
-        }
-
-        /** @hide */
-        abstract T self();
+    @MatchCriteria
+    public int getMetered() {
+        return mMeteredMatchCriteria;
     }
 }
diff --git a/core/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java b/core/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java
index 6bbb2bf..272ca9d 100644
--- a/core/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java
+++ b/core/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java
@@ -16,31 +16,59 @@
 package android.net.vcn;
 
 import static com.android.internal.annotations.VisibleForTesting.Visibility;
+import static com.android.server.vcn.util.PersistableBundleUtils.STRING_DESERIALIZER;
+import static com.android.server.vcn.util.PersistableBundleUtils.STRING_SERIALIZER;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.net.NetworkCapabilities;
 import android.os.PersistableBundle;
+import android.util.ArraySet;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.vcn.util.PersistableBundleUtils;
 
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Objects;
+import java.util.Set;
 
-// TODO: Add documents
-/** @hide */
+/**
+ * This class represents a configuration for a network template class of underlying Carrier WiFi
+ * networks.
+ *
+ * <p>See {@link VcnUnderlyingNetworkTemplate}
+ */
 public final class VcnWifiUnderlyingNetworkTemplate extends VcnUnderlyingNetworkTemplate {
-    private static final String SSID_KEY = "mSsid";
-    @Nullable private final String mSsid;
+    private static final String SSIDS_KEY = "mSsids";
+    @Nullable private final Set<String> mSsids;
 
     private VcnWifiUnderlyingNetworkTemplate(
-            int networkQuality, boolean allowMetered, String ssid) {
-        super(NETWORK_PRIORITY_TYPE_WIFI, networkQuality, allowMetered);
-        mSsid = ssid;
+            int networkQuality, int meteredMatchCriteria, Set<String> ssids) {
+        super(NETWORK_PRIORITY_TYPE_WIFI, networkQuality, meteredMatchCriteria);
+        mSsids = new ArraySet<>(ssids);
 
         validate();
     }
 
     /** @hide */
+    @Override
+    protected void validate() {
+        super.validate();
+        validateSsids(mSsids);
+    }
+
+    private static void validateSsids(Set<String> ssids) {
+        Objects.requireNonNull(ssids, "ssids is null");
+
+        for (String ssid : ssids) {
+            Objects.requireNonNull(ssid, "found null value ssid");
+        }
+    }
+
+    /** @hide */
     @NonNull
     @VisibleForTesting(visibility = Visibility.PROTECTED)
     public static VcnWifiUnderlyingNetworkTemplate fromPersistableBundle(
@@ -48,9 +76,14 @@
         Objects.requireNonNull(in, "PersistableBundle is null");
 
         final int networkQuality = in.getInt(NETWORK_QUALITY_KEY);
-        final boolean allowMetered = in.getBoolean(ALLOW_METERED_KEY);
-        final String ssid = in.getString(SSID_KEY);
-        return new VcnWifiUnderlyingNetworkTemplate(networkQuality, allowMetered, ssid);
+        final int meteredMatchCriteria = in.getInt(METERED_MATCH_KEY);
+
+        final PersistableBundle ssidsBundle = in.getPersistableBundle(SSIDS_KEY);
+        Objects.requireNonNull(ssidsBundle, "ssidsBundle is null");
+        final Set<String> ssids =
+                new ArraySet<String>(
+                        PersistableBundleUtils.toList(ssidsBundle, STRING_DESERIALIZER));
+        return new VcnWifiUnderlyingNetworkTemplate(networkQuality, meteredMatchCriteria, ssids);
     }
 
     /** @hide */
@@ -59,13 +92,17 @@
     @VisibleForTesting(visibility = Visibility.PROTECTED)
     public PersistableBundle toPersistableBundle() {
         final PersistableBundle result = super.toPersistableBundle();
-        result.putString(SSID_KEY, mSsid);
+
+        final PersistableBundle ssidsBundle =
+                PersistableBundleUtils.fromList(new ArrayList<>(mSsids), STRING_SERIALIZER);
+        result.putPersistableBundle(SSIDS_KEY, ssidsBundle);
+
         return result;
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(super.hashCode(), mSsid);
+        return Objects.hash(super.hashCode(), mSsids);
     }
 
     @Override
@@ -79,49 +116,95 @@
         }
 
         final VcnWifiUnderlyingNetworkTemplate rhs = (VcnWifiUnderlyingNetworkTemplate) other;
-        return mSsid.equals(rhs.mSsid);
+        return mSsids.equals(rhs.mSsids);
     }
 
     /** @hide */
     @Override
     void dumpTransportSpecificFields(IndentingPrintWriter pw) {
-        pw.println("mSsid: " + mSsid);
+        pw.println("mSsids: " + mSsids);
     }
 
-    /** Retrieve the required SSID, or {@code null} if there is no requirement on SSID. */
-    @Nullable
-    public String getSsid() {
-        return mSsid;
+    /**
+     * Retrieve the matching SSIDs, or an empty set if any SSID is acceptable.
+     *
+     * @see Builder#setSsids(Set)
+     */
+    @NonNull
+    public Set<String> getSsids() {
+        return Collections.unmodifiableSet(mSsids);
     }
 
     /** This class is used to incrementally build VcnWifiUnderlyingNetworkTemplate objects. */
-    public static class Builder extends VcnUnderlyingNetworkTemplate.Builder<Builder> {
-        @Nullable private String mSsid;
+    public static final class Builder {
+        private int mNetworkQuality = NETWORK_QUALITY_ANY;
+        private int mMeteredMatchCriteria = MATCH_ANY;
+        @NonNull private final Set<String> mSsids = new ArraySet<>();
 
         /** Construct a Builder object. */
         public Builder() {}
 
         /**
-         * Set the required SSID.
+         * Set the required network quality to match this template.
          *
-         * @param ssid the required SSID, or {@code null} if any SSID is acceptable.
+         * <p>Network quality is a aggregation of multiple signals that reflect the network link
+         * metrics. For example, the network validation bit (see {@link
+         * NetworkCapabilities#NET_CAPABILITY_VALIDATED}), estimated first hop transport bandwidth
+         * and signal strength.
+         *
+         * @param networkQuality the required network quality. Defaults to NETWORK_QUALITY_ANY
+         * @hide
          */
         @NonNull
-        public Builder setSsid(@Nullable String ssid) {
-            mSsid = ssid;
+        public Builder setNetworkQuality(@NetworkQuality int networkQuality) {
+            validateNetworkQuality(networkQuality);
+
+            mNetworkQuality = networkQuality;
+            return this;
+        }
+
+        /**
+         * Set the matching criteria for metered networks.
+         *
+         * <p>A template where setMetered(MATCH_REQUIRED) will only match metered networks (one
+         * without NET_CAPABILITY_NOT_METERED). A template where setMetered(MATCH_FORBIDDEN) will
+         * only match a network that is not metered (one with NET_CAPABILITY_NOT_METERED).
+         *
+         * @param matchCriteria the matching criteria for metered networks. Defaults to {@link
+         *     #MATCH_ANY}.
+         * @see NetworkCapabilities#NET_CAPABILITY_NOT_METERED
+         */
+        // The matching getter is defined in the super class. Please see {@link
+        // VcnUnderlyingNetworkTemplate#getMetered()}
+        @SuppressLint("MissingGetterMatchingBuilder")
+        @NonNull
+        public Builder setMetered(@MatchCriteria int matchCriteria) {
+            validateMatchCriteria(matchCriteria, "setMetered");
+
+            mMeteredMatchCriteria = matchCriteria;
+            return this;
+        }
+
+        /**
+         * Set the SSIDs with which a network can match this priority rule.
+         *
+         * @param ssids the matching SSIDs. Network with one of the matching SSIDs can match this
+         *     priority rule. If the set is empty, any SSID will match. The default is an empty set.
+         */
+        @NonNull
+        public Builder setSsids(@NonNull Set<String> ssids) {
+            validateSsids(ssids);
+
+            mSsids.clear();
+            mSsids.addAll(ssids);
             return this;
         }
 
         /** Build the VcnWifiUnderlyingNetworkTemplate. */
         @NonNull
         public VcnWifiUnderlyingNetworkTemplate build() {
-            return new VcnWifiUnderlyingNetworkTemplate(mNetworkQuality, mAllowMetered, mSsid);
-        }
-
-        /** @hide */
-        @Override
-        Builder self() {
-            return this;
+            return new VcnWifiUnderlyingNetworkTemplate(
+                    mNetworkQuality, mMeteredMatchCriteria, mSsids);
         }
     }
 }
diff --git a/core/java/android/service/games/GameService.java b/core/java/android/service/games/GameService.java
index b79c055..105c2aa 100644
--- a/core/java/android/service/games/GameService.java
+++ b/core/java/android/service/games/GameService.java
@@ -38,10 +38,23 @@
  * when a game session should begin. It is always kept running by the system.
  * Because of this it should be kept as lightweight as possible.
  *
- * Heavy weight operations (such as showing UI) should be implemented in the
+ * <p>Heavyweight operations (such as showing UI) should be implemented in the
  * associated {@link GameSessionService} when a game session is taking place. Its
  * implementation should run in a separate process from the {@link GameService}.
  *
+ * <p>To extend this class, you must declare the service in your manifest file with
+ * the {@link android.Manifest.permission#BIND_GAME_SERVICE} permission
+ * and include an intent filter with the {@link #SERVICE_INTERFACE} action. For example:
+ * <pre>
+ * &lt;service android:name=".GameService"
+ *          android:label="&#64;string/service_name"
+ *          android:permission="android.permission.BIND_GAME_SERVICE">
+ *     &lt;intent-filter>
+ *         &lt;action android:name="android.service.games.GameService" />
+ *     &lt;/intent-filter>
+ * &lt;/service>
+ * </pre>
+ *
  * @hide
  */
 @SystemApi
@@ -90,7 +103,7 @@
 
     @Override
     @Nullable
-    public IBinder onBind(@Nullable Intent intent) {
+    public final IBinder onBind(@Nullable Intent intent) {
         if (ACTION_GAME_SERVICE.equals(intent.getAction())) {
             return mInterface.asBinder();
         }
diff --git a/core/java/android/service/games/GameSessionService.java b/core/java/android/service/games/GameSessionService.java
index a2c8870..c1a3eb5 100644
--- a/core/java/android/service/games/GameSessionService.java
+++ b/core/java/android/service/games/GameSessionService.java
@@ -73,7 +73,7 @@
 
     @Override
     @Nullable
-    public IBinder onBind(@Nullable Intent intent) {
+    public final IBinder onBind(@Nullable Intent intent) {
         if (intent == null) {
             return null;
         }
diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java
index 07d5fc5..25e0eca 100644
--- a/core/java/android/view/ViewDebug.java
+++ b/core/java/android/view/ViewDebug.java
@@ -1896,7 +1896,7 @@
 
         private Canvas mCanvas;
         private Bitmap mBitmap;
-        private boolean mEnabledHwBitmapsInSwMode;
+        private boolean mEnabledHwFeaturesInSwMode;
 
         @Override
         public Canvas getCanvas(View view, int width, int height) {
@@ -1913,7 +1913,7 @@
             if (mCanvas == null) {
                 mCanvas = new Canvas();
             }
-            mEnabledHwBitmapsInSwMode = mCanvas.isHwBitmapsInSwModeEnabled();
+            mEnabledHwFeaturesInSwMode = mCanvas.isHwFeaturesInSwModeEnabled();
             mCanvas.setBitmap(mBitmap);
             return mCanvas;
         }
@@ -1921,7 +1921,7 @@
         @Override
         public Bitmap createBitmap() {
             mCanvas.setBitmap(null);
-            mCanvas.setHwBitmapsInSwModeEnabled(mEnabledHwBitmapsInSwMode);
+            mCanvas.setHwFeaturesInSwModeEnabled(mEnabledHwFeaturesInSwMode);
             return mBitmap;
         }
     }
diff --git a/core/java/android/window/DisplayAreaOrganizer.java b/core/java/android/window/DisplayAreaOrganizer.java
index 974a1dd..88ece5c 100644
--- a/core/java/android/window/DisplayAreaOrganizer.java
+++ b/core/java/android/window/DisplayAreaOrganizer.java
@@ -101,14 +101,6 @@
     public static final int FEATURE_IME_PLACEHOLDER = FEATURE_SYSTEM_FIRST + 7;
 
     /**
-     * Display area for one handed background layer, which preventing when user
-     * turning the Dark theme on, they can not clearly identify the screen has entered
-     * one handed mode.
-     * @hide
-     */
-    public static final int FEATURE_ONE_HANDED_BACKGROUND_PANEL = FEATURE_SYSTEM_FIRST + 8;
-
-    /**
      * Display area hosting IME window tokens (@see ImeContainer). By default, IMEs are parented
      * to FEATURE_IME_PLACEHOLDER but can be reparented under other RootDisplayArea.
      *
@@ -118,7 +110,7 @@
      * app on another screen).
      * @hide
      */
-    public static final int FEATURE_IME = FEATURE_SYSTEM_FIRST + 9;
+    public static final int FEATURE_IME = FEATURE_SYSTEM_FIRST + 8;
 
     /**
      * The last boundary of display area for system features
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 025f711..be7388b 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -759,11 +759,11 @@
         }
 
         try {
-            IBinder permissionToken = ActivityTaskManager.getService()
-                    .requestStartActivityPermissionToken(getActivityToken());
             Intent delegationIntent = new Intent();
             final ComponentName delegateActivity = ComponentName.unflattenFromString(
                     Resources.getSystem().getString(R.string.config_chooserActivity));
+            IBinder permissionToken = ActivityTaskManager.getService()
+                    .requestStartActivityPermissionToken(delegateActivity);
             delegationIntent.setComponent(delegateActivity);
             delegationIntent.putExtra(Intent.EXTRA_INTENT, getIntent());
             delegationIntent.putExtra(ActivityTaskManager.EXTRA_PERMISSION_TOKEN, permissionToken);
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index b273f6d..f9a8c7b 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -1437,11 +1437,11 @@
         try {
             // TODO: Once this is a small springboard activity, it can move off the UI process
             // and we can move the request method to ActivityManagerInternal.
-            IBinder permissionToken = ActivityTaskManager.getService()
-                    .requestStartActivityPermissionToken(getActivityToken());
             final Intent chooserIntent = new Intent();
             final ComponentName delegateActivity = ComponentName.unflattenFromString(
                     Resources.getSystem().getString(R.string.config_chooserActivity));
+            IBinder permissionToken = ActivityTaskManager.getService()
+                    .requestStartActivityPermissionToken(delegateActivity);
             chooserIntent.setClassName(delegateActivity.getPackageName(),
                     delegateActivity.getClassName());
             chooserIntent.putExtra(ActivityTaskManager.EXTRA_PERMISSION_TOKEN, permissionToken);
diff --git a/core/java/com/android/internal/net/VpnConfig.java b/core/java/com/android/internal/net/VpnConfig.java
index 2a203ac..b579be0 100644
--- a/core/java/com/android/internal/net/VpnConfig.java
+++ b/core/java/com/android/internal/net/VpnConfig.java
@@ -34,8 +34,6 @@
 import android.os.Parcelable;
 import android.os.UserHandle;
 
-import java.net.Inet4Address;
-import java.net.InetAddress;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -93,8 +91,8 @@
     public String interfaze;
     public String session;
     public int mtu = -1;
-    public List<LinkAddress> addresses = new ArrayList<LinkAddress>();
-    public List<RouteInfo> routes = new ArrayList<RouteInfo>();
+    public List<LinkAddress> addresses = new ArrayList<>();
+    public List<RouteInfo> routes = new ArrayList<>();
     public List<String> dnsServers;
     public List<String> searchDomains;
     public List<String> allowedApplications;
@@ -114,12 +112,32 @@
     public VpnConfig() {
     }
 
-    public void updateAllowedFamilies(InetAddress address) {
-        if (address instanceof Inet4Address) {
-            allowIPv4 = true;
-        } else {
-            allowIPv6 = true;
-        }
+    public VpnConfig(VpnConfig other) {
+        user = other.user;
+        interfaze = other.interfaze;
+        session = other.session;
+        mtu = other.mtu;
+        addresses = copyOf(other.addresses);
+        routes = copyOf(other.routes);
+        dnsServers = copyOf(other.dnsServers);
+        searchDomains = copyOf(other.searchDomains);
+        allowedApplications = copyOf(other.allowedApplications);
+        disallowedApplications = copyOf(other.disallowedApplications);
+        configureIntent = other.configureIntent;
+        startTime = other.startTime;
+        legacy = other.legacy;
+        blocking = other.blocking;
+        allowBypass = other.allowBypass;
+        allowIPv4 = other.allowIPv4;
+        allowIPv6 = other.allowIPv6;
+        isMetered = other.isMetered;
+        underlyingNetworks = other.underlyingNetworks != null ? Arrays.copyOf(
+                other.underlyingNetworks, other.underlyingNetworks.length) : null;
+        proxyInfo = other.proxyInfo;
+    }
+
+    private static <T> List<T> copyOf(List<T> list) {
+        return list != null ? new ArrayList<>(list) : null;
     }
 
     public void addLegacyRoutes(String routesStr) {
@@ -131,7 +149,6 @@
             //each route is ip/prefix
             RouteInfo info = new RouteInfo(new IpPrefix(route), null, null, RouteInfo.RTN_UNICAST);
             this.routes.add(info);
-            updateAllowedFamilies(info.getDestination().getAddress());
         }
     }
 
@@ -144,7 +161,6 @@
             //each address is ip/prefix
             LinkAddress addr = new LinkAddress(address);
             this.addresses.add(addr);
-            updateAllowedFamilies(addr.getAddress());
         }
     }
 
diff --git a/core/java/com/android/internal/widget/PointerLocationView.java b/core/java/com/android/internal/widget/PointerLocationView.java
index 627381c..09ff4e0 100644
--- a/core/java/com/android/internal/widget/PointerLocationView.java
+++ b/core/java/com/android/internal/widget/PointerLocationView.java
@@ -37,6 +37,7 @@
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.MotionEvent.PointerCoords;
+import android.view.RoundedCorner;
 import android.view.VelocityTracker;
 import android.view.View;
 import android.view.ViewConfiguration;
@@ -229,13 +230,29 @@
 
     @Override
     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
-        if (insets.getDisplayCutout() != null) {
-            mHeaderPaddingTop = insets.getDisplayCutout().getSafeInsetTop();
-            mWaterfallInsets = insets.getDisplayCutout().getWaterfallInsets();
-        } else {
-            mHeaderPaddingTop = 0;
-            mWaterfallInsets = Insets.NONE;
+        int headerPaddingTop = 0;
+        Insets waterfallInsets = Insets.NONE;
+
+        final RoundedCorner topLeftRounded =
+                insets.getRoundedCorner(RoundedCorner.POSITION_TOP_LEFT);
+        if (topLeftRounded != null) {
+            headerPaddingTop = topLeftRounded.getRadius();
         }
+
+        final RoundedCorner topRightRounded =
+                insets.getRoundedCorner(RoundedCorner.POSITION_TOP_RIGHT);
+        if (topRightRounded != null) {
+            headerPaddingTop = Math.max(headerPaddingTop, topRightRounded.getRadius());
+        }
+
+        if (insets.getDisplayCutout() != null) {
+            headerPaddingTop =
+                    Math.max(headerPaddingTop, insets.getDisplayCutout().getSafeInsetTop());
+            waterfallInsets = insets.getDisplayCutout().getWaterfallInsets();
+        }
+
+        mHeaderPaddingTop = headerPaddingTop;
+        mWaterfallInsets = waterfallInsets;
         return super.onApplyWindowInsets(insets);
     }
 
diff --git a/graphics/java/android/graphics/BaseCanvas.java b/graphics/java/android/graphics/BaseCanvas.java
index a612265..425a378 100644
--- a/graphics/java/android/graphics/BaseCanvas.java
+++ b/graphics/java/android/graphics/BaseCanvas.java
@@ -67,7 +67,7 @@
      * @hide
      */
     protected int mDensity = Bitmap.DENSITY_NONE;
-    private boolean mAllowHwBitmapsInSwMode = false;
+    private boolean mAllowHwFeaturesInSwMode = false;
 
     protected void throwIfCannotDraw(Bitmap bitmap) {
         if (bitmap.isRecycled()) {
@@ -101,14 +101,14 @@
 
     public void drawArc(float left, float top, float right, float bottom, float startAngle,
             float sweepAngle, boolean useCenter, @NonNull Paint paint) {
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         nDrawArc(mNativeCanvasWrapper, left, top, right, bottom, startAngle, sweepAngle,
                 useCenter, paint.getNativeInstance());
     }
 
     public void drawArc(@NonNull RectF oval, float startAngle, float sweepAngle, boolean useCenter,
             @NonNull Paint paint) {
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         drawArc(oval.left, oval.top, oval.right, oval.bottom, startAngle, sweepAngle, useCenter,
                 paint);
     }
@@ -119,14 +119,14 @@
 
     public void drawBitmap(@NonNull Bitmap bitmap, float left, float top, @Nullable Paint paint) {
         throwIfCannotDraw(bitmap);
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         nDrawBitmap(mNativeCanvasWrapper, bitmap.getNativeInstance(), left, top,
                 paint != null ? paint.getNativeInstance() : 0, mDensity, mScreenDensity,
                 bitmap.mDensity);
     }
 
     public void drawBitmap(@NonNull Bitmap bitmap, @NonNull Matrix matrix, @Nullable Paint paint) {
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         nDrawBitmapMatrix(mNativeCanvasWrapper, bitmap.getNativeInstance(), matrix.ni(),
                 paint != null ? paint.getNativeInstance() : 0);
     }
@@ -137,7 +137,7 @@
             throw new NullPointerException();
         }
         throwIfCannotDraw(bitmap);
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         final long nativePaint = paint == null ? 0 : paint.getNativeInstance();
 
         int left, top, right, bottom;
@@ -163,7 +163,7 @@
             throw new NullPointerException();
         }
         throwIfCannotDraw(bitmap);
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         final long nativePaint = paint == null ? 0 : paint.getNativeInstance();
 
         float left, top, right, bottom;
@@ -202,7 +202,7 @@
                 || (lastScanline + width > length)) {
             throw new ArrayIndexOutOfBoundsException();
         }
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         // quick escape if there's nothing to draw
         if (width == 0 || height == 0) {
             return;
@@ -226,7 +226,7 @@
         if ((meshWidth | meshHeight | vertOffset | colorOffset) < 0) {
             throw new ArrayIndexOutOfBoundsException();
         }
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         if (meshWidth == 0 || meshHeight == 0) {
             return;
         }
@@ -243,7 +243,7 @@
     }
 
     public void drawCircle(float cx, float cy, float radius, @NonNull Paint paint) {
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         nDrawCircle(mNativeCanvasWrapper, cx, cy, radius, paint.getNativeInstance());
     }
 
@@ -275,23 +275,23 @@
 
     public void drawLine(float startX, float startY, float stopX, float stopY,
             @NonNull Paint paint) {
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         nDrawLine(mNativeCanvasWrapper, startX, startY, stopX, stopY, paint.getNativeInstance());
     }
 
     public void drawLines(@Size(multiple = 4) @NonNull float[] pts, int offset, int count,
             @NonNull Paint paint) {
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         nDrawLines(mNativeCanvasWrapper, pts, offset, count, paint.getNativeInstance());
     }
 
     public void drawLines(@Size(multiple = 4) @NonNull float[] pts, @NonNull Paint paint) {
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         drawLines(pts, 0, pts.length, paint);
     }
 
     public void drawOval(float left, float top, float right, float bottom, @NonNull Paint paint) {
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         nDrawOval(mNativeCanvasWrapper, left, top, right, bottom, paint.getNativeInstance());
     }
 
@@ -299,18 +299,19 @@
         if (oval == null) {
             throw new NullPointerException();
         }
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         drawOval(oval.left, oval.top, oval.right, oval.bottom, paint);
     }
 
     public void drawPaint(@NonNull Paint paint) {
+        throwIfHasHwFeaturesInSwMode(paint);
         nDrawPaint(mNativeCanvasWrapper, paint.getNativeInstance());
     }
 
     public void drawPatch(@NonNull NinePatch patch, @NonNull Rect dst, @Nullable Paint paint) {
         Bitmap bitmap = patch.getBitmap();
         throwIfCannotDraw(bitmap);
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         final long nativePaint = paint == null ? 0 : paint.getNativeInstance();
         nDrawNinePatch(mNativeCanvasWrapper, bitmap.getNativeInstance(), patch.mNativeChunk,
                 dst.left, dst.top, dst.right, dst.bottom, nativePaint,
@@ -320,7 +321,7 @@
     public void drawPatch(@NonNull NinePatch patch, @NonNull RectF dst, @Nullable Paint paint) {
         Bitmap bitmap = patch.getBitmap();
         throwIfCannotDraw(bitmap);
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         final long nativePaint = paint == null ? 0 : paint.getNativeInstance();
         nDrawNinePatch(mNativeCanvasWrapper, bitmap.getNativeInstance(), patch.mNativeChunk,
                 dst.left, dst.top, dst.right, dst.bottom, nativePaint,
@@ -328,7 +329,7 @@
     }
 
     public void drawPath(@NonNull Path path, @NonNull Paint paint) {
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         if (path.isSimplePath && path.rects != null) {
             nDrawRegion(mNativeCanvasWrapper, path.rects.mNativeRegion, paint.getNativeInstance());
         } else {
@@ -337,18 +338,18 @@
     }
 
     public void drawPoint(float x, float y, @NonNull Paint paint) {
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         nDrawPoint(mNativeCanvasWrapper, x, y, paint.getNativeInstance());
     }
 
     public void drawPoints(@Size(multiple = 2) float[] pts, int offset, int count,
             @NonNull Paint paint) {
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         nDrawPoints(mNativeCanvasWrapper, pts, offset, count, paint.getNativeInstance());
     }
 
     public void drawPoints(@Size(multiple = 2) @NonNull float[] pts, @NonNull Paint paint) {
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         drawPoints(pts, 0, pts.length, paint);
     }
 
@@ -359,7 +360,7 @@
         if (index < 0 || index + count > text.length || count * 2 > pos.length) {
             throw new IndexOutOfBoundsException();
         }
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         for (int i = 0; i < count; i++) {
             drawText(text, index + i, 1, pos[i * 2], pos[i * 2 + 1], paint);
         }
@@ -368,22 +369,22 @@
     @Deprecated
     public void drawPosText(@NonNull String text, @NonNull @Size(multiple = 2) float[] pos,
             @NonNull Paint paint) {
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         drawPosText(text.toCharArray(), 0, text.length(), pos, paint);
     }
 
     public void drawRect(float left, float top, float right, float bottom, @NonNull Paint paint) {
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         nDrawRect(mNativeCanvasWrapper, left, top, right, bottom, paint.getNativeInstance());
     }
 
     public void drawRect(@NonNull Rect r, @NonNull Paint paint) {
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         drawRect(r.left, r.top, r.right, r.bottom, paint);
     }
 
     public void drawRect(@NonNull RectF rect, @NonNull Paint paint) {
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         nDrawRect(mNativeCanvasWrapper,
                 rect.left, rect.top, rect.right, rect.bottom, paint.getNativeInstance());
     }
@@ -394,13 +395,13 @@
 
     public void drawRoundRect(float left, float top, float right, float bottom, float rx, float ry,
             @NonNull Paint paint) {
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         nDrawRoundRect(mNativeCanvasWrapper, left, top, right, bottom, rx, ry,
                 paint.getNativeInstance());
     }
 
     public void drawRoundRect(@NonNull RectF rect, float rx, float ry, @NonNull Paint paint) {
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         drawRoundRect(rect.left, rect.top, rect.right, rect.bottom, rx, ry, paint);
     }
 
@@ -410,7 +411,7 @@
      */
     public void drawDoubleRoundRect(@NonNull RectF outer, float outerRx, float outerRy,
             @NonNull RectF inner, float innerRx, float innerRy, @NonNull Paint paint) {
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         float outerLeft = outer.left;
         float outerTop = outer.top;
         float outerRight = outer.right;
@@ -431,7 +432,7 @@
      */
     public void drawDoubleRoundRect(@NonNull RectF outer, @NonNull float[] outerRadii,
             @NonNull RectF inner, @NonNull float[] innerRadii, @NonNull Paint paint) {
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         if (innerRadii == null || outerRadii == null
                 || innerRadii.length != 8 || outerRadii.length != 8) {
             throw new IllegalArgumentException("Both inner and outer radii arrays must contain "
@@ -509,7 +510,7 @@
                 (text.length - index - count)) < 0) {
             throw new IndexOutOfBoundsException();
         }
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         nDrawText(mNativeCanvasWrapper, text, index, count, x, y, paint.mBidiFlags,
                 paint.getNativeInstance());
     }
@@ -519,7 +520,7 @@
         if ((start | end | (end - start) | (text.length() - end)) < 0) {
             throw new IndexOutOfBoundsException();
         }
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         if (text instanceof String || text instanceof SpannedString ||
                 text instanceof SpannableString) {
             nDrawText(mNativeCanvasWrapper, text.toString(), start, end, x, y,
@@ -537,7 +538,7 @@
     }
 
     public void drawText(@NonNull String text, float x, float y, @NonNull Paint paint) {
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         nDrawText(mNativeCanvasWrapper, text, 0, text.length(), x, y, paint.mBidiFlags,
                 paint.getNativeInstance());
     }
@@ -547,7 +548,7 @@
         if ((start | end | (end - start) | (text.length() - end)) < 0) {
             throw new IndexOutOfBoundsException();
         }
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         nDrawText(mNativeCanvasWrapper, text, start, end, x, y, paint.mBidiFlags,
                 paint.getNativeInstance());
     }
@@ -557,7 +558,7 @@
         if (index < 0 || index + count > text.length) {
             throw new ArrayIndexOutOfBoundsException();
         }
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         nDrawTextOnPath(mNativeCanvasWrapper, text, index, count,
                 path.readOnlyNI(), hOffset, vOffset,
                 paint.mBidiFlags, paint.getNativeInstance());
@@ -566,7 +567,7 @@
     public void drawTextOnPath(@NonNull String text, @NonNull Path path, float hOffset,
             float vOffset, @NonNull Paint paint) {
         if (text.length() > 0) {
-            throwIfHasHwBitmapInSwMode(paint);
+            throwIfHasHwFeaturesInSwMode(paint);
             nDrawTextOnPath(mNativeCanvasWrapper, text, path.readOnlyNI(), hOffset, vOffset,
                     paint.mBidiFlags, paint.getNativeInstance());
         }
@@ -587,7 +588,7 @@
             throw new IndexOutOfBoundsException();
         }
 
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         nDrawTextRun(mNativeCanvasWrapper, text, index, count, contextIndex, contextCount,
                 x, y, isRtl, paint.getNativeInstance(), 0 /* measured text */);
     }
@@ -606,7 +607,7 @@
             throw new IndexOutOfBoundsException();
         }
 
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         if (text instanceof String || text instanceof SpannedString ||
                 text instanceof SpannableString) {
             nDrawTextRun(mNativeCanvasWrapper, text.toString(), start, end, contextStart,
@@ -664,7 +665,7 @@
         if (indices != null) {
             checkRange(indices.length, indexOffset, indexCount);
         }
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         nDrawVertices(mNativeCanvasWrapper, mode.nativeInt, vertexCount, verts,
                 vertOffset, texs, texOffset, colors, colorOffset,
                 indices, indexOffset, indexCount, paint.getNativeInstance());
@@ -680,50 +681,52 @@
     /**
      * @hide
      */
-    public void setHwBitmapsInSwModeEnabled(boolean enabled) {
-        mAllowHwBitmapsInSwMode = enabled;
+    public void setHwFeaturesInSwModeEnabled(boolean enabled) {
+        mAllowHwFeaturesInSwMode = enabled;
     }
 
     /**
      * @hide
      */
-    public boolean isHwBitmapsInSwModeEnabled() {
-        return mAllowHwBitmapsInSwMode;
+    public boolean isHwFeaturesInSwModeEnabled() {
+        return mAllowHwFeaturesInSwMode;
     }
 
     /**
+     * If true throw an exception
      * @hide
      */
-    protected void onHwBitmapInSwMode() {
-        if (!mAllowHwBitmapsInSwMode) {
+    protected boolean onHwFeatureInSwMode() {
+        return !mAllowHwFeaturesInSwMode;
+    }
+
+    private void throwIfHwBitmapInSwMode(Bitmap bitmap) {
+        if (!isHardwareAccelerated() && bitmap.getConfig() == Bitmap.Config.HARDWARE
+                && onHwFeatureInSwMode()) {
             throw new IllegalArgumentException(
                     "Software rendering doesn't support hardware bitmaps");
         }
     }
 
-    private void throwIfHwBitmapInSwMode(Bitmap bitmap) {
-        if (!isHardwareAccelerated() && bitmap.getConfig() == Bitmap.Config.HARDWARE) {
-            onHwBitmapInSwMode();
-        }
-    }
-
-    private void throwIfHasHwBitmapInSwMode(Paint p) {
+    private void throwIfHasHwFeaturesInSwMode(Paint p) {
         if (isHardwareAccelerated() || p == null) {
             return;
         }
-        throwIfHasHwBitmapInSwMode(p.getShader());
+        throwIfHasHwFeaturesInSwMode(p.getShader());
     }
 
-    private void throwIfHasHwBitmapInSwMode(Shader shader) {
+    private void throwIfHasHwFeaturesInSwMode(Shader shader) {
         if (shader == null) {
             return;
         }
         if (shader instanceof BitmapShader) {
             throwIfHwBitmapInSwMode(((BitmapShader) shader).mBitmap);
-        }
-        if (shader instanceof ComposeShader) {
-            throwIfHasHwBitmapInSwMode(((ComposeShader) shader).mShaderA);
-            throwIfHasHwBitmapInSwMode(((ComposeShader) shader).mShaderB);
+        } else if (shader instanceof RuntimeShader && onHwFeatureInSwMode()) {
+            throw new IllegalArgumentException(
+                    "Software rendering doesn't support RuntimeShader");
+        } else if (shader instanceof ComposeShader) {
+            throwIfHasHwFeaturesInSwMode(((ComposeShader) shader).mShaderA);
+            throwIfHasHwFeaturesInSwMode(((ComposeShader) shader).mShaderB);
         }
     }
 
diff --git a/graphics/java/android/graphics/Picture.java b/graphics/java/android/graphics/Picture.java
index 390d3d4..ee4165b 100644
--- a/graphics/java/android/graphics/Picture.java
+++ b/graphics/java/android/graphics/Picture.java
@@ -124,7 +124,7 @@
     public void endRecording() {
         verifyValid();
         if (mRecordingCanvas != null) {
-            mRequiresHwAcceleration = mRecordingCanvas.mHoldsHwBitmap;
+            mRequiresHwAcceleration = mRecordingCanvas.mUsesHwFeature;
             mRecordingCanvas = null;
             nativeEndRecording(mNativePicture);
         }
@@ -182,8 +182,10 @@
         if (mRecordingCanvas != null) {
             endRecording();
         }
-        if (mRequiresHwAcceleration && !canvas.isHardwareAccelerated()) {
-            canvas.onHwBitmapInSwMode();
+        if (mRequiresHwAcceleration && !canvas.isHardwareAccelerated()
+                && canvas.onHwFeatureInSwMode()) {
+            throw new IllegalArgumentException("Software rendering not supported for Pictures that"
+                    + " require hardware acceleration");
         }
         nativeDraw(canvas.getNativeCanvasWrapper(), mNativePicture);
     }
@@ -242,7 +244,7 @@
 
     private static class PictureCanvas extends Canvas {
         private final Picture mPicture;
-        boolean mHoldsHwBitmap;
+        boolean mUsesHwFeature;
 
         public PictureCanvas(Picture pict, long nativeCanvas) {
             super(nativeCanvas);
@@ -265,8 +267,9 @@
         }
 
         @Override
-        protected void onHwBitmapInSwMode() {
-            mHoldsHwBitmap = true;
+        protected boolean onHwFeatureInSwMode() {
+            mUsesHwFeature = true;
+            return false;
         }
     }
 }
diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java
index b843589..ffaa4ea 100644
--- a/graphics/java/android/graphics/drawable/RippleDrawable.java
+++ b/graphics/java/android/graphics/drawable/RippleDrawable.java
@@ -868,7 +868,7 @@
     private void drawPatterned(@NonNull Canvas canvas) {
         final Rect bounds = mHotspotBounds;
         final int saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
-        boolean useCanvasProps = shouldUseCanvasProps(canvas);
+        boolean useCanvasProps = !mForceSoftware;
         if (isBounded()) {
             canvas.clipRect(getDirtyBounds());
         }
@@ -914,7 +914,11 @@
         }
         for (int i = 0; i < mRunningAnimations.size(); i++) {
             RippleAnimationSession s = mRunningAnimations.get(i);
-            if (useCanvasProps) {
+            if (!canvas.isHardwareAccelerated()) {
+                Log.e(TAG, "The RippleDrawable.STYLE_PATTERNED animation is not supported for a "
+                        + "non-hardware accelerated Canvas. Skipping animation.");
+                break;
+            } else if (useCanvasProps) {
                 RippleAnimationSession.AnimationProperties<CanvasProperty<Float>,
                         CanvasProperty<Paint>>
                         p = s.getCanvasProperties();
@@ -1002,10 +1006,6 @@
         return color;
     }
 
-    private boolean shouldUseCanvasProps(Canvas c) {
-        return !mForceSoftware && c.isHardwareAccelerated();
-    }
-
     @Override
     public void invalidateSelf() {
         invalidateSelf(true);
diff --git a/libs/WindowManager/Shell/res/layout/background_panel.xml b/libs/WindowManager/Shell/res/layout/background_panel.xml
new file mode 100644
index 0000000..c3569d8
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/background_panel.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  ~ Copyright (C) 2022 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
+  -->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/background_panel_layout"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    android:gravity="center_horizontal | center_vertical"
+    android:background="@android:color/transparent">
+</LinearLayout>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
index ad9ebb2..36e55ba 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
@@ -137,14 +137,16 @@
             return;
         }
 
-        if (mIcon == null) {
-            // TODO: add fade-in animation.
+        if (mBackgroundLeash == null) {
             mBackgroundLeash = SurfaceUtils.makeColorLayer(mHostLeash,
                     RESIZING_BACKGROUND_SURFACE_NAME, mSurfaceSession);
             t.setColor(mBackgroundLeash, getResizingBackgroundColor(resizingTask))
                     .setLayer(mBackgroundLeash, SPLIT_DIVIDER_LAYER - 1)
                     .show(mBackgroundLeash);
+        }
 
+        if (mIcon == null && resizingTask.topActivityInfo != null) {
+            // TODO: add fade-in animation.
             mIcon = mIconProvider.getIcon(resizingTask.topActivityInfo);
             mResizingIconView.setImageDrawable(mIcon);
             mResizingIconView.setVisibility(View.VISIBLE);
@@ -168,12 +170,16 @@
             return;
         }
 
+        if (mBackgroundLeash != null) {
+            t.remove(mBackgroundLeash);
+            mBackgroundLeash = null;
+        }
+
         if (mIcon != null) {
             mResizingIconView.setVisibility(View.GONE);
             mResizingIconView.setImageDrawable(null);
-            t.remove(mBackgroundLeash).hide(mIconLeash);
+            t.hide(mIconLeash);
             mIcon = null;
-            mBackgroundLeash = null;
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/BackgroundWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/BackgroundWindowManager.java
new file mode 100644
index 0000000..c20b7d9
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/BackgroundWindowManager.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 2022 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.wm.shell.onehanded;
+
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
+import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY;
+import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
+
+import static com.android.wm.shell.onehanded.OneHandedState.STATE_ACTIVE;
+import static com.android.wm.shell.onehanded.OneHandedState.STATE_ENTERING;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Color;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.os.Binder;
+import android.util.Slog;
+import android.view.ContextThemeWrapper;
+import android.view.IWindow;
+import android.view.LayoutInflater;
+import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
+import android.view.SurfaceSession;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.WindowlessWindowManager;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.wm.shell.R;
+import com.android.wm.shell.common.DisplayLayout;
+
+import java.io.PrintWriter;
+
+/**
+ * Holds view hierarchy of a root surface and helps inflate a themeable view for background.
+ */
+public final class BackgroundWindowManager extends WindowlessWindowManager {
+    private static final String TAG = BackgroundWindowManager.class.getSimpleName();
+    private static final int THEME_COLOR_OFFSET = 10;
+
+    private final OneHandedSurfaceTransactionHelper.SurfaceControlTransactionFactory
+            mTransactionFactory;
+
+    private Context mContext;
+    private Rect mDisplayBounds;
+    private SurfaceControlViewHost mViewHost;
+    private SurfaceControl mLeash;
+    private View mBackgroundView;
+    private @OneHandedState.State int mCurrentState;
+
+    public BackgroundWindowManager(Context context) {
+        super(context.getResources().getConfiguration(), null /* rootSurface */,
+                null /* hostInputToken */);
+        mContext = context;
+        mTransactionFactory = SurfaceControl.Transaction::new;
+    }
+
+    @Override
+    public SurfaceControl getSurfaceControl(IWindow window) {
+        return super.getSurfaceControl(window);
+    }
+
+    @Override
+    public void setConfiguration(Configuration configuration) {
+        super.setConfiguration(configuration);
+        mContext = mContext.createConfigurationContext(configuration);
+    }
+
+    /**
+     * onConfigurationChanged events for updating background theme color.
+     */
+    public void onConfigurationChanged() {
+        if (mCurrentState == STATE_ENTERING || mCurrentState == STATE_ACTIVE) {
+            updateThemeOnly();
+        }
+    }
+
+    /**
+     * One-handed mode state changed callback
+     * @param newState of One-handed mode representing by {@link OneHandedState}
+     */
+    public void onStateChanged(int newState) {
+        mCurrentState = newState;
+    }
+
+    @Override
+    protected void attachToParentSurface(IWindow window, SurfaceControl.Builder b) {
+        final SurfaceControl.Builder builder = new SurfaceControl.Builder(new SurfaceSession())
+                .setColorLayer()
+                .setBufferSize(mDisplayBounds.width(), mDisplayBounds.height())
+                .setFormat(PixelFormat.RGB_888)
+                .setOpaque(true)
+                .setName(TAG)
+                .setCallsite("BackgroundWindowManager#attachToParentSurface");
+        mLeash = builder.build();
+        b.setParent(mLeash);
+    }
+
+    /** Inflates background view on to the root surface. */
+    boolean initView() {
+        if (mBackgroundView != null || mViewHost != null) {
+            return false;
+        }
+
+        mViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(), this);
+        mBackgroundView = (View) LayoutInflater.from(mContext)
+                .inflate(R.layout.background_panel, null /* root */);
+        WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+                mDisplayBounds.width(), mDisplayBounds.height(), 0 /* TYPE NONE */,
+                FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL | FLAG_WATCH_OUTSIDE_TOUCH
+                        | FLAG_SLIPPERY, PixelFormat.TRANSLUCENT);
+        lp.token = new Binder();
+        lp.setTitle("background-panel");
+        lp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY;
+        mBackgroundView.setBackgroundColor(getThemeColorForBackground());
+        mViewHost.setView(mBackgroundView, lp);
+        return true;
+    }
+
+    /**
+     * Called when onDisplayAdded() or onDisplayRemoved() callback.
+     * @param displayLayout The latest {@link DisplayLayout} for display bounds.
+     */
+    public void onDisplayChanged(DisplayLayout displayLayout) {
+        // One-handed mode is only available on portrait.
+        if (displayLayout.height() > displayLayout.width()) {
+            mDisplayBounds = new Rect(0, 0, displayLayout.width(), displayLayout.height());
+        } else {
+            mDisplayBounds = new Rect(0, 0, displayLayout.height(), displayLayout.width());
+        }
+    }
+
+    private void updateThemeOnly() {
+        if (mBackgroundView == null || mViewHost == null || mLeash == null) {
+            Slog.w(TAG, "Background view or SurfaceControl does not exist when trying to "
+                    + "update theme only!");
+            return;
+        }
+
+        WindowManager.LayoutParams lp = (WindowManager.LayoutParams)
+                mBackgroundView.getLayoutParams();
+        mBackgroundView.setBackgroundColor(getThemeColorForBackground());
+        mViewHost.setView(mBackgroundView, lp);
+    }
+
+    /**
+     * Shows the background layer when One-handed mode triggered.
+     */
+    public void showBackgroundLayer() {
+        if (!initView()) {
+            updateThemeOnly();
+            return;
+        }
+        if (mLeash == null) {
+            Slog.w(TAG, "SurfaceControl mLeash is null, can't show One-handed mode "
+                    + "background panel!");
+            return;
+        }
+
+        mTransactionFactory.getTransaction()
+                .setAlpha(mLeash, 1.0f)
+                .setLayer(mLeash, -1 /* at bottom-most layer */)
+                .show(mLeash)
+                .apply();
+    }
+
+    /**
+     * Remove the leash of background layer after stop One-handed mode.
+     */
+    public void removeBackgroundLayer() {
+        if (mBackgroundView != null) {
+            mBackgroundView = null;
+        }
+
+        if (mViewHost != null) {
+            mViewHost.release();
+            mViewHost = null;
+        }
+
+        if (mLeash != null) {
+            mTransactionFactory.getTransaction().remove(mLeash).apply();
+            mLeash = null;
+        }
+    }
+
+    /**
+     * Gets {@link SurfaceControl} of the background layer.
+     * @return {@code null} if not exist.
+     */
+    @Nullable
+    SurfaceControl getSurfaceControl() {
+        return mLeash;
+    }
+
+    private int getThemeColor() {
+        final Context themedContext = new ContextThemeWrapper(mContext,
+                com.android.internal.R.style.Theme_DeviceDefault_DayNight);
+        return themedContext.getColor(R.color.one_handed_tutorial_background_color);
+    }
+
+    int getThemeColorForBackground() {
+        final int origThemeColor = getThemeColor();
+        return android.graphics.Color.argb(Color.alpha(origThemeColor),
+                Color.red(origThemeColor) - THEME_COLOR_OFFSET,
+                Color.green(origThemeColor) - THEME_COLOR_OFFSET,
+                Color.blue(origThemeColor) - THEME_COLOR_OFFSET);
+    }
+
+    private float adjustColor(int origColor) {
+        return Math.max(origColor - THEME_COLOR_OFFSET, 0) / 255.0f;
+    }
+
+    void dump(@NonNull PrintWriter pw) {
+        final String innerPrefix = "  ";
+        pw.println(TAG);
+        pw.print(innerPrefix + "mDisplayBounds=");
+        pw.println(mDisplayBounds);
+        pw.print(innerPrefix + "mViewHost=");
+        pw.println(mViewHost);
+        pw.print(innerPrefix + "mLeash=");
+        pw.println(mLeash);
+        pw.print(innerPrefix + "mBackgroundView=");
+        pw.println(mBackgroundView);
+    }
+
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java
deleted file mode 100644
index 9e1c61a..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java
+++ /dev/null
@@ -1,272 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.onehanded;
-
-import static com.android.wm.shell.onehanded.OneHandedState.STATE_ACTIVE;
-
-import android.animation.ValueAnimator;
-import android.content.Context;
-import android.graphics.Color;
-import android.graphics.PixelFormat;
-import android.graphics.Rect;
-import android.view.ContextThemeWrapper;
-import android.view.SurfaceControl;
-import android.view.SurfaceSession;
-import android.view.animation.LinearInterpolator;
-import android.window.DisplayAreaAppearedInfo;
-import android.window.DisplayAreaInfo;
-import android.window.DisplayAreaOrganizer;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
-
-import com.android.wm.shell.R;
-import com.android.wm.shell.common.DisplayLayout;
-
-import java.io.PrintWriter;
-import java.util.List;
-import java.util.concurrent.Executor;
-
-/**
- * Manages OneHanded color background layer areas.
- * To avoid when turning the Dark theme on, users can not clearly identify
- * the screen has entered one handed mode.
- */
-public class OneHandedBackgroundPanelOrganizer extends DisplayAreaOrganizer
-        implements OneHandedAnimationCallback, OneHandedState.OnStateChangedListener {
-    private static final String TAG = "OneHandedBackgroundPanelOrganizer";
-    private static final int THEME_COLOR_OFFSET = 10;
-    private static final int ALPHA_ANIMATION_DURATION = 200;
-
-    private final Context mContext;
-    private final SurfaceSession mSurfaceSession = new SurfaceSession();
-    private final OneHandedSurfaceTransactionHelper.SurfaceControlTransactionFactory
-            mTransactionFactory;
-
-    private @OneHandedState.State int mCurrentState;
-    private ValueAnimator mAlphaAnimator;
-
-    private float mTranslationFraction;
-    private float[] mThemeColor;
-
-    /**
-     * The background to distinguish the boundary of translated windows and empty region when
-     * one handed mode triggered.
-     */
-    private Rect mBkgBounds;
-    private Rect mStableInsets;
-
-    @Nullable
-    @VisibleForTesting
-    SurfaceControl mBackgroundSurface;
-    @Nullable
-    private SurfaceControl mParentLeash;
-
-    public OneHandedBackgroundPanelOrganizer(Context context, DisplayLayout displayLayout,
-            OneHandedSettingsUtil settingsUtil, Executor executor) {
-        super(executor);
-        mContext = context;
-        mTranslationFraction = settingsUtil.getTranslationFraction(context);
-        mTransactionFactory = SurfaceControl.Transaction::new;
-        updateThemeColors();
-    }
-
-    @Override
-    public void onDisplayAreaAppeared(@NonNull DisplayAreaInfo displayAreaInfo,
-            @NonNull SurfaceControl leash) {
-        mParentLeash = leash;
-    }
-
-    @Override
-    public List<DisplayAreaAppearedInfo> registerOrganizer(int displayAreaFeature) {
-        final List<DisplayAreaAppearedInfo> displayAreaInfos;
-        displayAreaInfos = super.registerOrganizer(displayAreaFeature);
-        for (int i = 0; i < displayAreaInfos.size(); i++) {
-            final DisplayAreaAppearedInfo info = displayAreaInfos.get(i);
-            onDisplayAreaAppeared(info.getDisplayAreaInfo(), info.getLeash());
-        }
-        return displayAreaInfos;
-    }
-
-    @Override
-    public void unregisterOrganizer() {
-        super.unregisterOrganizer();
-        removeBackgroundPanelLayer();
-        mParentLeash = null;
-    }
-
-    @Override
-    public void onAnimationUpdate(SurfaceControl.Transaction tx, float xPos, float yPos) {
-        final int yTopPos = (mStableInsets.top - mBkgBounds.height()) + Math.round(yPos);
-        tx.setPosition(mBackgroundSurface, 0, yTopPos);
-    }
-
-    @Nullable
-    @VisibleForTesting
-    boolean isRegistered() {
-        return mParentLeash != null;
-    }
-
-    void createBackgroundSurface() {
-        mBackgroundSurface = new SurfaceControl.Builder(mSurfaceSession)
-                .setBufferSize(mBkgBounds.width(), mBkgBounds.height())
-                .setColorLayer()
-                .setFormat(PixelFormat.RGB_888)
-                .setOpaque(true)
-                .setName("one-handed-background-panel")
-                .setCallsite("OneHandedBackgroundPanelOrganizer")
-                .build();
-
-        // TODO(185890335) Avoid Dimming for mid-range luminance wallpapers flash.
-        mAlphaAnimator = ValueAnimator.ofFloat(1.0f, 0.0f);
-        mAlphaAnimator.setInterpolator(new LinearInterpolator());
-        mAlphaAnimator.setDuration(ALPHA_ANIMATION_DURATION);
-        mAlphaAnimator.addUpdateListener(
-                animator -> detachBackgroundFromParent(animator));
-    }
-
-    void detachBackgroundFromParent(ValueAnimator animator) {
-        if (mBackgroundSurface == null || mParentLeash == null) {
-            return;
-        }
-        // TODO(185890335) Avoid Dimming for mid-range luminance wallpapers flash.
-        final float currentValue = (float) animator.getAnimatedValue();
-        final SurfaceControl.Transaction tx = mTransactionFactory.getTransaction();
-        if (currentValue == 0.0f) {
-            tx.reparent(mBackgroundSurface, null).apply();
-        } else {
-            tx.setAlpha(mBackgroundSurface, (float) animator.getAnimatedValue()).apply();
-        }
-    }
-
-    /**
-     * Called when onDisplayAdded() or onDisplayRemoved() callback.
-     *
-     * @param displayLayout The latest {@link DisplayLayout} representing current displayId
-     */
-    public void onDisplayChanged(DisplayLayout displayLayout) {
-        mStableInsets = displayLayout.stableInsets();
-        // Ensure the mBkgBounds is portrait, due to OHM only support on portrait
-        if (displayLayout.height() > displayLayout.width()) {
-            mBkgBounds = new Rect(0, 0, displayLayout.width(),
-                    Math.round(displayLayout.height() * mTranslationFraction) + mStableInsets.top);
-        } else {
-            mBkgBounds = new Rect(0, 0, displayLayout.height(),
-                    Math.round(displayLayout.width() * mTranslationFraction) + mStableInsets.top);
-        }
-    }
-
-    @VisibleForTesting
-    void onStart() {
-        if (mBackgroundSurface == null) {
-            createBackgroundSurface();
-        }
-        showBackgroundPanelLayer();
-    }
-
-    /**
-     * Called when transition finished.
-     */
-    public void onStopFinished() {
-        if (mAlphaAnimator == null) {
-            return;
-        }
-        mAlphaAnimator.start();
-    }
-
-    @VisibleForTesting
-    void showBackgroundPanelLayer() {
-        if (mParentLeash == null) {
-            return;
-        }
-
-        if (mBackgroundSurface == null) {
-            createBackgroundSurface();
-        }
-
-        // TODO(185890335) Avoid Dimming for mid-range luminance wallpapers flash.
-        if (mAlphaAnimator.isRunning()) {
-            mAlphaAnimator.end();
-        }
-
-        mTransactionFactory.getTransaction()
-                .reparent(mBackgroundSurface, mParentLeash)
-                .setAlpha(mBackgroundSurface, 1.0f)
-                .setLayer(mBackgroundSurface, -1 /* at bottom-most layer */)
-                .setColor(mBackgroundSurface, mThemeColor)
-                .show(mBackgroundSurface)
-                .apply();
-    }
-
-    @VisibleForTesting
-    void removeBackgroundPanelLayer() {
-        if (mBackgroundSurface == null) {
-            return;
-        }
-
-        mTransactionFactory.getTransaction()
-                .remove(mBackgroundSurface)
-                .apply();
-        mBackgroundSurface = null;
-    }
-
-    /**
-     * onConfigurationChanged events for updating tutorial text.
-     */
-    public void onConfigurationChanged() {
-        updateThemeColors();
-
-        if (mCurrentState != STATE_ACTIVE) {
-            return;
-        }
-        showBackgroundPanelLayer();
-    }
-
-    private void updateThemeColors() {
-        final Context themedContext = new ContextThemeWrapper(mContext,
-                com.android.internal.R.style.Theme_DeviceDefault_DayNight);
-        final int themeColor = themedContext.getColor(
-                R.color.one_handed_tutorial_background_color);
-        mThemeColor = new float[]{
-                adjustColor(Color.red(themeColor)),
-                adjustColor(Color.green(themeColor)),
-                adjustColor(Color.blue(themeColor))};
-    }
-
-    private float adjustColor(int origColor) {
-        return Math.max(origColor - THEME_COLOR_OFFSET, 0) / 255.0f;
-    }
-
-    @Override
-    public void onStateChanged(int newState) {
-        mCurrentState = newState;
-    }
-
-    void dump(@NonNull PrintWriter pw) {
-        final String innerPrefix = "  ";
-        pw.println(TAG);
-        pw.print(innerPrefix + "mBackgroundSurface=");
-        pw.println(mBackgroundSurface);
-        pw.print(innerPrefix + "mBkgBounds=");
-        pw.println(mBkgBounds);
-        pw.print(innerPrefix + "mThemeColor=");
-        pw.println(mThemeColor);
-        pw.print(innerPrefix + "mTranslationFraction=");
-        pw.println(mTranslationFraction);
-    }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
index 96f82fa..48acfc1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
@@ -99,7 +99,6 @@
 
     private OneHandedEventCallback mEventCallback;
     private OneHandedDisplayAreaOrganizer mDisplayAreaOrganizer;
-    private OneHandedBackgroundPanelOrganizer mBackgroundPanelOrganizer;
     private OneHandedUiEventLogger mOneHandedUiEventLogger;
 
     private final DisplayController.OnDisplaysChangedListener mDisplaysChangedListener =
@@ -163,7 +162,6 @@
                 public void onStopFinished(Rect bounds) {
                     mState.setState(STATE_NONE);
                     notifyShortcutStateChanged(STATE_NONE);
-                    mBackgroundPanelOrganizer.onStopFinished();
                 }
             };
 
@@ -201,32 +199,28 @@
         OneHandedAccessibilityUtil accessibilityUtil = new OneHandedAccessibilityUtil(context);
         OneHandedTimeoutHandler timeoutHandler = new OneHandedTimeoutHandler(mainExecutor);
         OneHandedState oneHandedState = new OneHandedState();
+        BackgroundWindowManager backgroundWindowManager = new BackgroundWindowManager(context);
         OneHandedTutorialHandler tutorialHandler = new OneHandedTutorialHandler(context,
-                settingsUtil, windowManager);
+                settingsUtil, windowManager, backgroundWindowManager);
         OneHandedAnimationController animationController =
                 new OneHandedAnimationController(context);
         OneHandedTouchHandler touchHandler = new OneHandedTouchHandler(timeoutHandler,
                 mainExecutor);
-        OneHandedBackgroundPanelOrganizer oneHandedBackgroundPanelOrganizer =
-                new OneHandedBackgroundPanelOrganizer(context, displayLayout, settingsUtil,
-                        mainExecutor);
         OneHandedDisplayAreaOrganizer organizer = new OneHandedDisplayAreaOrganizer(
                 context, displayLayout, settingsUtil, animationController, tutorialHandler,
-                oneHandedBackgroundPanelOrganizer, jankMonitor, mainExecutor);
+                jankMonitor, mainExecutor);
         OneHandedUiEventLogger oneHandedUiEventsLogger = new OneHandedUiEventLogger(uiEventLogger);
         IOverlayManager overlayManager = IOverlayManager.Stub.asInterface(
                 ServiceManager.getService(Context.OVERLAY_SERVICE));
-        return new OneHandedController(context, displayController,
-                oneHandedBackgroundPanelOrganizer, organizer, touchHandler, tutorialHandler,
-                settingsUtil, accessibilityUtil, timeoutHandler, oneHandedState, jankMonitor,
-                oneHandedUiEventsLogger, overlayManager, taskStackListener, mainExecutor,
-                mainHandler);
+        return new OneHandedController(context, displayController, organizer, touchHandler,
+                tutorialHandler, settingsUtil, accessibilityUtil, timeoutHandler, oneHandedState,
+                jankMonitor, oneHandedUiEventsLogger, overlayManager, taskStackListener,
+                mainExecutor, mainHandler);
     }
 
     @VisibleForTesting
     OneHandedController(Context context,
             DisplayController displayController,
-            OneHandedBackgroundPanelOrganizer backgroundPanelOrganizer,
             OneHandedDisplayAreaOrganizer displayAreaOrganizer,
             OneHandedTouchHandler touchHandler,
             OneHandedTutorialHandler tutorialHandler,
@@ -243,7 +237,6 @@
         mContext = context;
         mOneHandedSettingsUtil = settingsUtil;
         mOneHandedAccessibilityUtil = oneHandedAccessibilityUtil;
-        mBackgroundPanelOrganizer = backgroundPanelOrganizer;
         mDisplayAreaOrganizer = displayAreaOrganizer;
         mDisplayController = displayController;
         mTouchHandler = touchHandler;
@@ -286,7 +279,6 @@
         mAccessibilityManager.addAccessibilityStateChangeListener(
                 mAccessibilityStateChangeListener);
 
-        mState.addSListeners(mBackgroundPanelOrganizer);
         mState.addSListeners(mTutorialHandler);
     }
 
@@ -368,7 +360,6 @@
                 mDisplayAreaOrganizer.getDisplayLayout().height() * mOffSetFraction);
         mOneHandedAccessibilityUtil.announcementForScreenReader(
                 mOneHandedAccessibilityUtil.getOneHandedStartDescription());
-        mBackgroundPanelOrganizer.onStart();
         mDisplayAreaOrganizer.scheduleOffset(0, yOffSet);
         mTimeoutHandler.resetTimer();
         mOneHandedUiEventLogger.writeEvent(
@@ -461,7 +452,6 @@
         }
         mDisplayAreaOrganizer.setDisplayLayout(newDisplayLayout);
         mTutorialHandler.onDisplayChanged(newDisplayLayout);
-        mBackgroundPanelOrganizer.onDisplayChanged(newDisplayLayout);
     }
 
     private ContentObserver getObserver(Runnable onChangeRunnable) {
@@ -585,7 +575,6 @@
 
         if (!mIsOneHandedEnabled) {
             mDisplayAreaOrganizer.unregisterOrganizer();
-            mBackgroundPanelOrganizer.unregisterOrganizer();
             // Do NOT register + unRegister DA in the same call
             return;
         }
@@ -594,11 +583,6 @@
             mDisplayAreaOrganizer.registerOrganizer(
                     OneHandedDisplayAreaOrganizer.FEATURE_ONE_HANDED);
         }
-
-        if (!mBackgroundPanelOrganizer.isRegistered()) {
-            mBackgroundPanelOrganizer.registerOrganizer(
-                    OneHandedBackgroundPanelOrganizer.FEATURE_ONE_HANDED_BACKGROUND_PANEL);
-        }
     }
 
     @VisibleForTesting
@@ -613,13 +597,12 @@
     }
 
     private void onConfigChanged(Configuration newConfig) {
-        if (mTutorialHandler == null || mBackgroundPanelOrganizer == null) {
+        if (mTutorialHandler == null) {
             return;
         }
         if (!mIsOneHandedEnabled || newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
             return;
         }
-        mBackgroundPanelOrganizer.onConfigurationChanged();
         mTutorialHandler.onConfigurationChanged();
     }
 
@@ -650,10 +633,6 @@
         pw.print(innerPrefix + "mIsSwipeToNotificationEnabled=");
         pw.println(mIsSwipeToNotificationEnabled);
 
-        if (mBackgroundPanelOrganizer != null) {
-            mBackgroundPanelOrganizer.dump(pw);
-        }
-
         if (mDisplayAreaOrganizer != null) {
             mDisplayAreaOrganizer.dump(pw);
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
index 87eb40c..f61d1b9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
@@ -80,7 +80,6 @@
             mSurfaceControlTransactionFactory;
     private OneHandedTutorialHandler mTutorialHandler;
     private List<OneHandedTransitionCallback> mTransitionCallbacks = new ArrayList<>();
-    private OneHandedBackgroundPanelOrganizer mBackgroundPanelOrganizer;
 
     @VisibleForTesting
     OneHandedAnimationCallback mOneHandedAnimationCallback =
@@ -135,7 +134,6 @@
             OneHandedSettingsUtil oneHandedSettingsUtil,
             OneHandedAnimationController animationController,
             OneHandedTutorialHandler tutorialHandler,
-            OneHandedBackgroundPanelOrganizer oneHandedBackgroundGradientOrganizer,
             InteractionJankMonitor jankMonitor,
             ShellExecutor mainExecutor) {
         super(mainExecutor);
@@ -150,7 +148,6 @@
                 SystemProperties.getInt(ONE_HANDED_MODE_TRANSLATE_ANIMATION_DURATION,
                         animationDurationConfig);
         mSurfaceControlTransactionFactory = SurfaceControl.Transaction::new;
-        mBackgroundPanelOrganizer = oneHandedBackgroundGradientOrganizer;
         mTutorialHandler = tutorialHandler;
     }
 
@@ -258,7 +255,6 @@
             animator.setTransitionDirection(direction)
                     .addOneHandedAnimationCallback(mOneHandedAnimationCallback)
                     .addOneHandedAnimationCallback(mTutorialHandler)
-                    .addOneHandedAnimationCallback(mBackgroundPanelOrganizer)
                     .setDuration(durationMs)
                     .start();
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java
index 88f3375..04e8cf9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java
@@ -32,7 +32,6 @@
 import android.content.res.TypedArray;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
-import android.os.SystemProperties;
 import android.view.ContextThemeWrapper;
 import android.view.Gravity;
 import android.view.LayoutInflater;
@@ -65,6 +64,7 @@
 
     private final float mTutorialHeightRatio;
     private final WindowManager mWindowManager;
+    private final BackgroundWindowManager mBackgroundWindowManager;
 
     private @OneHandedState.State int mCurrentState;
     private int mTutorialAreaHeight;
@@ -79,9 +79,10 @@
     private int mAlphaAnimationDurationMs;
 
     public OneHandedTutorialHandler(Context context, OneHandedSettingsUtil settingsUtil,
-            WindowManager windowManager) {
+            WindowManager windowManager, BackgroundWindowManager backgroundWindowManager) {
         mContext = context;
         mWindowManager = windowManager;
+        mBackgroundWindowManager = backgroundWindowManager;
         mTutorialHeightRatio = settingsUtil.getTranslationFraction(context);
         mAlphaAnimationDurationMs = settingsUtil.getTransitionDuration(context);
     }
@@ -110,8 +111,19 @@
     }
 
     @Override
+    public void onStartFinished(Rect bounds) {
+        fillBackgroundColor();
+    }
+
+    @Override
+    public void onStopFinished(Rect bounds) {
+        removeBackgroundSurface();
+    }
+
+    @Override
     public void onStateChanged(int newState) {
         mCurrentState = newState;
+        mBackgroundWindowManager.onStateChanged(newState);
         switch (newState) {
             case STATE_ENTERING:
                 createViewAndAttachToWindow(mContext);
@@ -126,7 +138,6 @@
             case STATE_NONE:
                 checkTransitionEnd();
                 removeTutorialFromWindowManager();
-                break;
             default:
                 break;
         }
@@ -146,6 +157,7 @@
         }
         mTutorialAreaHeight = Math.round(mDisplayBounds.height() * mTutorialHeightRatio);
         mAlphaTransitionStart = mTutorialAreaHeight * START_TRANSITION_FRACTION;
+        mBackgroundWindowManager.onDisplayChanged(displayLayout);
     }
 
     @VisibleForTesting
@@ -169,6 +181,7 @@
     private void attachTargetToWindow() {
         try {
             mWindowManager.addView(mTargetViewContainer, getTutorialTargetLayoutParams());
+            mBackgroundWindowManager.showBackgroundLayer();
         } catch (IllegalStateException e) {
             // This shouldn't happen, but if the target is already added, just update its
             // layout params.
@@ -186,6 +199,11 @@
         mTargetViewContainer = null;
     }
 
+    @VisibleForTesting
+    void removeBackgroundSurface() {
+        mBackgroundWindowManager.removeBackgroundLayer();
+    }
+
     /**
      * Returns layout params for the dismiss target, using the latest display metrics.
      */
@@ -213,9 +231,12 @@
      * onConfigurationChanged events for updating tutorial text.
      */
     public void onConfigurationChanged() {
+        mBackgroundWindowManager.onConfigurationChanged();
+
         removeTutorialFromWindowManager();
         if (mCurrentState == STATE_ENTERING || mCurrentState == STATE_ACTIVE) {
             createViewAndAttachToWindow(mContext);
+            fillBackgroundColor();
             updateThemeColor();
             checkTransitionEnd();
         }
@@ -247,6 +268,14 @@
         tutorialDesc.setTextColor(themedTextColorSecondary);
     }
 
+    private void fillBackgroundColor() {
+        if (mTargetViewContainer == null || mBackgroundWindowManager == null) {
+            return;
+        }
+        mTargetViewContainer.setBackgroundColor(
+                mBackgroundWindowManager.getThemeColorForBackground());
+    }
+
     private void setupAlphaTransition(boolean isEntering) {
         final float start = isEntering ? 0.0f : 1.0f;
         final float end = isEntering ? 1.0f : 0.0f;
@@ -282,5 +311,9 @@
         pw.println(mAlphaTransitionStart);
         pw.print(innerPrefix + "mAlphaAnimationDurationMs=");
         pw.println(mAlphaAnimationDurationMs);
+
+        if (mBackgroundWindowManager != null) {
+            mBackgroundWindowManager.dump(pw);
+        }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/BackgroundWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/BackgroundWindowManagerTest.java
new file mode 100644
index 0000000..f3f7067
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/BackgroundWindowManagerTest.java
@@ -0,0 +1,61 @@
+/**
+ * Copyright (C) 2022 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.wm.shell.onehanded;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.testing.TestableLooper;
+
+import androidx.test.annotation.UiThreadTest;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.DisplayLayout;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/** Tests for {@link BackgroundWindowManager} */
+@SmallTest
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@RunWith(AndroidJUnit4.class)
+public class BackgroundWindowManagerTest extends ShellTestCase {
+    private BackgroundWindowManager mBackgroundWindowManager;
+    @Mock
+    private DisplayLayout  mMockDisplayLayout;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mBackgroundWindowManager = new BackgroundWindowManager(mContext);
+        mBackgroundWindowManager.onDisplayChanged(mMockDisplayLayout);
+    }
+
+    @Test
+    @UiThreadTest
+    public void testInitRelease() {
+        mBackgroundWindowManager.initView();
+        assertThat(mBackgroundWindowManager.getSurfaceControl()).isNotNull();
+
+        mBackgroundWindowManager.removeBackgroundLayer();
+        assertThat(mBackgroundWindowManager.getSurfaceControl()).isNull();
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizerTest.java
deleted file mode 100644
index 7b9553c..0000000
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizerTest.java
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.onehanded;
-
-import static android.view.Display.DEFAULT_DISPLAY;
-import static android.window.DisplayAreaOrganizer.FEATURE_ONE_HANDED_BACKGROUND_PANEL;
-
-import static com.android.wm.shell.onehanded.OneHandedState.STATE_ACTIVE;
-import static com.android.wm.shell.onehanded.OneHandedState.STATE_NONE;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.view.Display;
-import android.view.SurfaceControl;
-import android.window.DisplayAreaInfo;
-import android.window.IWindowContainerToken;
-import android.window.WindowContainerToken;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.wm.shell.common.DisplayController;
-import com.android.wm.shell.common.DisplayLayout;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
-public class OneHandedBackgroundPanelOrganizerTest extends OneHandedTestCase {
-    private DisplayAreaInfo mDisplayAreaInfo;
-    private Display mDisplay;
-    private DisplayLayout mDisplayLayout;
-    private OneHandedBackgroundPanelOrganizer mSpiedBackgroundPanelOrganizer;
-    private WindowContainerToken mToken;
-    private SurfaceControl mLeash;
-
-    @Mock
-    IWindowContainerToken mMockRealToken;
-    @Mock
-    DisplayController mMockDisplayController;
-    @Mock
-    OneHandedSettingsUtil mMockSettingsUtil;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        mToken = new WindowContainerToken(mMockRealToken);
-        mLeash = new SurfaceControl();
-        mDisplay = mContext.getDisplay();
-        mDisplayLayout = new DisplayLayout(mContext, mDisplay);
-        when(mMockDisplayController.getDisplay(anyInt())).thenReturn(mDisplay);
-        mDisplayAreaInfo = new DisplayAreaInfo(mToken, DEFAULT_DISPLAY,
-                FEATURE_ONE_HANDED_BACKGROUND_PANEL);
-
-        mSpiedBackgroundPanelOrganizer = spy(
-                new OneHandedBackgroundPanelOrganizer(mContext, mDisplayLayout, mMockSettingsUtil,
-                        Runnable::run));
-        mSpiedBackgroundPanelOrganizer.onDisplayChanged(mDisplayLayout);
-    }
-
-    @Test
-    public void testOnDisplayAreaAppeared() {
-        mSpiedBackgroundPanelOrganizer.onDisplayAreaAppeared(mDisplayAreaInfo, mLeash);
-
-        assertThat(mSpiedBackgroundPanelOrganizer.isRegistered()).isTrue();
-        verify(mSpiedBackgroundPanelOrganizer, never()).showBackgroundPanelLayer();
-    }
-
-    @Test
-    public void testShowBackgroundLayer() {
-        mSpiedBackgroundPanelOrganizer.onDisplayAreaAppeared(mDisplayAreaInfo, null);
-        mSpiedBackgroundPanelOrganizer.onStart();
-
-        verify(mSpiedBackgroundPanelOrganizer).showBackgroundPanelLayer();
-    }
-
-    @Test
-    public void testRemoveBackgroundLayer() {
-        mSpiedBackgroundPanelOrganizer.onDisplayAreaAppeared(mDisplayAreaInfo, mLeash);
-
-        assertThat(mSpiedBackgroundPanelOrganizer.isRegistered()).isNotNull();
-
-        reset(mSpiedBackgroundPanelOrganizer);
-        mSpiedBackgroundPanelOrganizer.removeBackgroundPanelLayer();
-
-        assertThat(mSpiedBackgroundPanelOrganizer.mBackgroundSurface).isNull();
-    }
-
-    @Test
-    public void testStateNone_onConfigurationChanged() {
-        mSpiedBackgroundPanelOrganizer.onStateChanged(STATE_NONE);
-        mSpiedBackgroundPanelOrganizer.onConfigurationChanged();
-
-        verify(mSpiedBackgroundPanelOrganizer, never()).showBackgroundPanelLayer();
-    }
-
-    @Test
-    public void testStateActivate_onConfigurationChanged() {
-        mSpiedBackgroundPanelOrganizer.onStateChanged(STATE_ACTIVE);
-        mSpiedBackgroundPanelOrganizer.onConfigurationChanged();
-
-        verify(mSpiedBackgroundPanelOrganizer).showBackgroundPanelLayer();
-    }
-}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
index 636e875..2886b97 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
@@ -73,8 +73,6 @@
     @Mock
     DisplayController mMockDisplayController;
     @Mock
-    OneHandedBackgroundPanelOrganizer mMockBackgroundOrganizer;
-    @Mock
     OneHandedDisplayAreaOrganizer mMockDisplayAreaOrganizer;
     @Mock
     OneHandedEventCallback mMockEventCallback;
@@ -115,7 +113,6 @@
         when(mMockDisplayController.getDisplayLayout(anyInt())).thenReturn(null);
         when(mMockDisplayAreaOrganizer.getDisplayAreaTokenMap()).thenReturn(new ArrayMap<>());
         when(mMockDisplayAreaOrganizer.isReady()).thenReturn(true);
-        when(mMockBackgroundOrganizer.isRegistered()).thenReturn(true);
         when(mMockSettingsUitl.getSettingsOneHandedModeEnabled(any(), anyInt())).thenReturn(
                 mDefaultEnabled);
         when(mMockSettingsUitl.getSettingsOneHandedModeTimeout(any(), anyInt())).thenReturn(
@@ -134,7 +131,6 @@
         mSpiedOneHandedController = spy(new OneHandedController(
                 mContext,
                 mMockDisplayController,
-                mMockBackgroundOrganizer,
                 mMockDisplayAreaOrganizer,
                 mMockTouchHandler,
                 mMockTutorialHandler,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java
index df21163..9c7f723 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java
@@ -95,8 +95,6 @@
     @Mock
     WindowContainerTransaction mMockWindowContainerTransaction;
     @Mock
-    OneHandedBackgroundPanelOrganizer mMockBackgroundOrganizer;
-    @Mock
     ShellExecutor mMockShellMainExecutor;
     @Mock
     OneHandedSettingsUtil mMockSettingsUitl;
@@ -143,7 +141,6 @@
                 mMockSettingsUitl,
                 mMockAnimationController,
                 mTutorialHandler,
-                mMockBackgroundOrganizer,
                 mJankMonitor,
                 mMockShellMainExecutor));
 
@@ -431,7 +428,6 @@
                         mMockSettingsUitl,
                         mMockAnimationController,
                         mTutorialHandler,
-                        mMockBackgroundOrganizer,
                         mJankMonitor,
                         mMockShellMainExecutor));
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedStateTest.java
index 58399b6..dba1b8b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedStateTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedStateTest.java
@@ -67,8 +67,6 @@
     @Mock
     DisplayController mMockDisplayController;
     @Mock
-    OneHandedBackgroundPanelOrganizer mMockBackgroundOrganizer;
-    @Mock
     OneHandedDisplayAreaOrganizer mMockDisplayAreaOrganizer;
     @Mock
     OneHandedTouchHandler mMockTouchHandler;
@@ -105,7 +103,6 @@
 
         when(mMockDisplayController.getDisplay(anyInt())).thenReturn(mDisplay);
         when(mMockDisplayAreaOrganizer.getDisplayAreaTokenMap()).thenReturn(new ArrayMap<>());
-        when(mMockBackgroundOrganizer.isRegistered()).thenReturn(true);
         when(mMockSettingsUitl.getSettingsOneHandedModeEnabled(any(), anyInt())).thenReturn(
                 mDefaultEnabled);
         when(mMockSettingsUitl.getSettingsOneHandedModeTimeout(any(), anyInt())).thenReturn(
@@ -123,7 +120,6 @@
         mSpiedOneHandedController = spy(new OneHandedController(
                 mContext,
                 mMockDisplayController,
-                mMockBackgroundOrganizer,
                 mMockDisplayAreaOrganizer,
                 mMockTouchHandler,
                 mMockTutorialHandler,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java
index b1434ca..63d8bfd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java
@@ -56,6 +56,8 @@
     OneHandedSettingsUtil mMockSettingsUtil;
     @Mock
     WindowManager mMockWindowManager;
+    @Mock
+    BackgroundWindowManager mMockBackgroundWindowManager;
 
     @Before
     public void setUp() {
@@ -63,10 +65,11 @@
         when(mMockSettingsUtil.getTutorialShownCounts(any(), anyInt())).thenReturn(0);
 
         mDisplay = mContext.getDisplay();
-        mDisplayLayout = new DisplayLayout(mContext, mDisplay);
+        mDisplayLayout = new DisplayLayout(getTestContext().getApplicationContext(), mDisplay);
         mSpiedTransitionState = spy(new OneHandedState());
         mSpiedTutorialHandler = spy(
-                new OneHandedTutorialHandler(mContext, mMockSettingsUtil, mMockWindowManager));
+                new OneHandedTutorialHandler(mContext, mMockSettingsUtil, mMockWindowManager,
+                        mMockBackgroundWindowManager));
         mTimeoutHandler = new OneHandedTimeoutHandler(mMockShellMainExecutor);
     }
 
diff --git a/media/OWNERS b/media/OWNERS
index 0aff43e..5f50137 100644
--- a/media/OWNERS
+++ b/media/OWNERS
@@ -3,7 +3,6 @@
 essick@google.com
 etalvala@google.com
 hdmoon@google.com
-hkuang@google.com
 hunga@google.com
 insun@google.com
 jaewan@google.com
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index ad86002..f3670e6 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -764,6 +764,9 @@
                 @Override
                 public void run() {
                     mSessionCallback.onVideoAvailable(mSession);
+                    if (mSession.mIAppNotificationEnabled && mSession.getIAppSession() != null) {
+                        mSession.getIAppSession().notifyVideoAvailable();
+                    }
                 }
             });
         }
@@ -773,6 +776,9 @@
                 @Override
                 public void run() {
                     mSessionCallback.onVideoUnavailable(mSession, reason);
+                    if (mSession.mIAppNotificationEnabled && mSession.getIAppSession() != null) {
+                        mSession.getIAppSession().notifyVideoUnavailable(reason);
+                    }
                 }
             });
         }
@@ -782,6 +788,9 @@
                 @Override
                 public void run() {
                     mSessionCallback.onContentAllowed(mSession);
+                    if (mSession.mIAppNotificationEnabled && mSession.getIAppSession() != null) {
+                        mSession.getIAppSession().notifyContentAllowed();
+                    }
                 }
             });
         }
@@ -791,6 +800,9 @@
                 @Override
                 public void run() {
                     mSessionCallback.onContentBlocked(mSession, rating);
+                    if (mSession.mIAppNotificationEnabled && mSession.getIAppSession() != null) {
+                        mSession.getIAppSession().notifyContentBlocked(rating);
+                    }
                 }
             });
         }
diff --git a/media/java/android/media/tv/interactive/ITvIAppManager.aidl b/media/java/android/media/tv/interactive/ITvIAppManager.aidl
index 23201fa..2e04359 100644
--- a/media/java/android/media/tv/interactive/ITvIAppManager.aidl
+++ b/media/java/android/media/tv/interactive/ITvIAppManager.aidl
@@ -51,6 +51,10 @@
     void notifyTuned(in IBinder sessionToken, in Uri channelUri, int userId);
     void notifyTrackSelected(in IBinder sessionToken, int type, in String trackId, int userId);
     void notifyTracksChanged(in IBinder sessionToken, in List<TvTrackInfo> tracks, int userId);
+    void notifyVideoAvailable(in IBinder sessionToken, int userId);
+    void notifyVideoUnavailable(in IBinder sessionToken, int reason, int userId);
+    void notifyContentAllowed(in IBinder sessionToken, int userId);
+    void notifyContentBlocked(in IBinder sessionToken, in String rating, int userId);
     void setSurface(in IBinder sessionToken, in Surface surface, int userId);
     void dispatchSurfaceChanged(in IBinder sessionToken, int format, int width, int height,
             int userId);
diff --git a/media/java/android/media/tv/interactive/ITvIAppSession.aidl b/media/java/android/media/tv/interactive/ITvIAppSession.aidl
index 52f9a87..2788ff6 100644
--- a/media/java/android/media/tv/interactive/ITvIAppSession.aidl
+++ b/media/java/android/media/tv/interactive/ITvIAppSession.aidl
@@ -42,6 +42,10 @@
     void notifyTuned(in Uri channelUri);
     void notifyTrackSelected(int type, in String trackId);
     void notifyTracksChanged(in List<TvTrackInfo> tracks);
+    void notifyVideoAvailable();
+    void notifyVideoUnavailable(int reason);
+    void notifyContentAllowed();
+    void notifyContentBlocked(in String rating);
     void setSurface(in Surface surface);
     void dispatchSurfaceChanged(int format, int width, int height);
     void notifyBroadcastInfoResponse(in BroadcastInfoResponse response);
diff --git a/media/java/android/media/tv/interactive/TvIAppManager.java b/media/java/android/media/tv/interactive/TvIAppManager.java
index d1fd1df..9685e3a 100644
--- a/media/java/android/media/tv/interactive/TvIAppManager.java
+++ b/media/java/android/media/tv/interactive/TvIAppManager.java
@@ -26,6 +26,7 @@
 import android.media.tv.AdResponse;
 import android.media.tv.BroadcastInfoRequest;
 import android.media.tv.BroadcastInfoResponse;
+import android.media.tv.TvContentRating;
 import android.media.tv.TvInputManager;
 import android.media.tv.TvTrackInfo;
 import android.net.Uri;
@@ -1037,6 +1038,66 @@
             }
         }
 
+        /**
+         * Notifies IAPP session when video is available.
+         */
+        public void notifyVideoAvailable() {
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.notifyVideoAvailable(mToken, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        /**
+         * Notifies IAPP session when video is unavailable.
+         */
+        public void notifyVideoUnavailable(int reason) {
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.notifyVideoUnavailable(mToken, reason, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        /**
+         * Notifies IAPP session when content is allowed.
+         */
+        public void notifyContentAllowed() {
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.notifyContentAllowed(mToken, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        /**
+         * Notifies IAPP session when content is blocked.
+         */
+        public void notifyContentBlocked(TvContentRating rating) {
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.notifyContentBlocked(mToken, rating.flattenToString(), mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
         private void flushPendingEventsLocked() {
             mHandler.removeMessages(InputEventHandler.MSG_FLUSH_INPUT_EVENT);
 
diff --git a/media/java/android/media/tv/interactive/TvIAppService.java b/media/java/android/media/tv/interactive/TvIAppService.java
index 0456041..4993bc3 100644
--- a/media/java/android/media/tv/interactive/TvIAppService.java
+++ b/media/java/android/media/tv/interactive/TvIAppService.java
@@ -31,6 +31,7 @@
 import android.media.tv.AdResponse;
 import android.media.tv.BroadcastInfoRequest;
 import android.media.tv.BroadcastInfoResponse;
+import android.media.tv.TvContentRating;
 import android.media.tv.TvTrackInfo;
 import android.net.Uri;
 import android.os.AsyncTask;
@@ -442,6 +443,34 @@
         }
 
         /**
+         * Called when video is available.
+         * @hide
+         */
+        public void onVideoAvailable() {
+        }
+
+        /**
+         * Called when video is unavailable.
+         * @hide
+         */
+        public void onVideoUnavailable(int reason) {
+        }
+
+        /**
+         * Called when content is allowed.
+         * @hide
+         */
+        public void onContentAllowed() {
+        }
+
+        /**
+         * Called when content is blocked.
+         * @hide
+         */
+        public void onContentBlocked(TvContentRating rating) {
+        }
+
+        /**
          * Called when a broadcast info response is received.
          * @hide
          */
@@ -816,6 +845,33 @@
             onTracksChanged(tracks);
         }
 
+        void notifyVideoAvailable() {
+            if (DEBUG) {
+                Log.d(TAG, "notifyVideoAvailable");
+            }
+            onVideoAvailable();
+        }
+
+        void notifyVideoUnavailable(int reason) {
+            if (DEBUG) {
+                Log.d(TAG, "notifyVideoAvailable (reason=" + reason + ")");
+            }
+            onVideoUnavailable(reason);
+        }
+
+        void notifyContentAllowed() {
+            if (DEBUG) {
+                Log.d(TAG, "notifyContentAllowed");
+            }
+            notifyContentAllowed();
+        }
+
+        void notifyContentBlocked(TvContentRating rating) {
+            if (DEBUG) {
+                Log.d(TAG, "notifyContentBlocked (rating=" + rating.flattenToString() + ")");
+            }
+            onContentBlocked(rating);
+        }
 
         /**
          * Calls {@link #onBroadcastInfoResponse}.
@@ -1182,6 +1238,26 @@
         }
 
         @Override
+        public void notifyVideoAvailable() {
+            mSessionImpl.notifyVideoAvailable();
+        }
+
+        @Override
+        public void notifyVideoUnavailable(int reason) {
+            mSessionImpl.notifyVideoUnavailable(reason);
+        }
+
+        @Override
+        public void notifyContentAllowed() {
+            mSessionImpl.notifyContentAllowed();
+        }
+
+        @Override
+        public void notifyContentBlocked(String rating) {
+            mSessionImpl.notifyContentBlocked(TvContentRating.unflattenFromString(rating));
+        }
+
+        @Override
         public void setSurface(Surface surface) {
             mSessionImpl.setSurface(surface);
         }
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index 9572e180..d9feaf40 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -461,6 +461,7 @@
 }
 
 MediaEvent::~MediaEvent() {
+    android::Mutex::Autolock autoLock(mLock);
     JNIEnv *env = AndroidRuntime::getJNIEnv();
     env->DeleteWeakGlobalRef(mMediaEventObj);
     mMediaEventObj = nullptr;
diff --git a/media/jni/tuner/FilterClient.cpp b/media/jni/tuner/FilterClient.cpp
index 8568383..959e756 100644
--- a/media/jni/tuner/FilterClient.cpp
+++ b/media/jni/tuner/FilterClient.cpp
@@ -43,6 +43,7 @@
 }
 
 FilterClient::~FilterClient() {
+    Mutex::Autolock _l(mLock);
     mTunerFilter = nullptr;
     mAvSharedHandle = nullptr;
     mAvSharedMemSize = 0;
@@ -74,6 +75,7 @@
     Result res;
     checkIsPassthroughFilter(configure);
 
+    Mutex::Autolock _l(mLock);
     if (mTunerFilter != nullptr) {
         Status s = mTunerFilter->configure(configure);
         res = ClientHelper::getServiceSpecificErrorCode(s);
@@ -87,6 +89,7 @@
 }
 
 Result FilterClient::configureMonitorEvent(int32_t monitorEventType) {
+    Mutex::Autolock _l(mLock);
     if (mTunerFilter != nullptr) {
         Status s = mTunerFilter->configureMonitorEvent(monitorEventType);
         return ClientHelper::getServiceSpecificErrorCode(s);
@@ -96,6 +99,7 @@
 }
 
 Result FilterClient::configureIpFilterContextId(int32_t cid) {
+    Mutex::Autolock _l(mLock);
     if (mTunerFilter != nullptr) {
         Status s = mTunerFilter->configureIpFilterContextId(cid);
         return ClientHelper::getServiceSpecificErrorCode(s);
@@ -105,6 +109,7 @@
 }
 
 Result FilterClient::configureAvStreamType(AvStreamType avStreamType) {
+    Mutex::Autolock _l(mLock);
     if (mTunerFilter != nullptr) {
         Status s = mTunerFilter->configureAvStreamType(avStreamType);
         return ClientHelper::getServiceSpecificErrorCode(s);
@@ -114,6 +119,7 @@
 }
 
 Result FilterClient::start() {
+    Mutex::Autolock _l(mLock);
     if (mTunerFilter != nullptr) {
         Status s = mTunerFilter->start();
         return ClientHelper::getServiceSpecificErrorCode(s);
@@ -123,6 +129,7 @@
 }
 
 Result FilterClient::stop() {
+    Mutex::Autolock _l(mLock);
     if (mTunerFilter != nullptr) {
         Status s = mTunerFilter->stop();
         return ClientHelper::getServiceSpecificErrorCode(s);
@@ -132,6 +139,7 @@
 }
 
 Result FilterClient::flush() {
+    Mutex::Autolock _l(mLock);
     if (mTunerFilter != nullptr) {
         Status s = mTunerFilter->flush();
         return ClientHelper::getServiceSpecificErrorCode(s);
@@ -141,6 +149,7 @@
 }
 
 Result FilterClient::getId(int32_t& id) {
+    Mutex::Autolock _l(mLock);
     if (mTunerFilter != nullptr) {
         Status s = mTunerFilter->getId(&id);
         return ClientHelper::getServiceSpecificErrorCode(s);
@@ -150,6 +159,7 @@
 }
 
 Result FilterClient::getId64Bit(int64_t& id) {
+    Mutex::Autolock _l(mLock);
     if (mTunerFilter != nullptr) {
         Status s = mTunerFilter->getId64Bit(&id);
         return ClientHelper::getServiceSpecificErrorCode(s);
@@ -159,6 +169,7 @@
 }
 
 Result FilterClient::releaseAvHandle(native_handle_t* handle, uint64_t avDataId) {
+    Mutex::Autolock _l(mLock);
     if (mTunerFilter != nullptr) {
         Status s = mTunerFilter->releaseAvHandle(dupToAidl(handle), avDataId);
         return ClientHelper::getServiceSpecificErrorCode(s);
@@ -168,6 +179,7 @@
 }
 
 Result FilterClient::setDataSource(sp<FilterClient> filterClient){
+    Mutex::Autolock _l(mLock);
     if (mTunerFilter != nullptr) {
         Status s = mTunerFilter->setDataSource(filterClient->getAidlFilter());
         return ClientHelper::getServiceSpecificErrorCode(s);
@@ -177,6 +189,7 @@
 }
 
 Result FilterClient::close() {
+    Mutex::Autolock _l(mLock);
     if (mFilterMQEventFlag != nullptr) {
         EventFlag::deleteEventFlag(&mFilterMQEventFlag);
         mFilterMQEventFlag = nullptr;
@@ -197,6 +210,7 @@
 }
 
 string FilterClient::acquireSharedFilterToken() {
+    Mutex::Autolock _l(mLock);
     if (mTunerFilter != nullptr) {
         string filterToken;
         if (mTunerFilter->acquireSharedFilterToken(&filterToken).isOk()) {
@@ -208,6 +222,7 @@
 }
 
 Result FilterClient::freeSharedFilterToken(const string& filterToken) {
+    Mutex::Autolock _l(mLock);
     if (mTunerFilter != nullptr) {
         Status s = mTunerFilter->freeSharedFilterToken(filterToken);
         return ClientHelper::getServiceSpecificErrorCode(s);
@@ -237,6 +252,7 @@
 }
 
 Result FilterClient::getFilterMq() {
+    Mutex::Autolock _l(mLock);
     if (mFilterMQ != nullptr) {
         return Result::SUCCESS;
     }
diff --git a/media/jni/tuner/FilterClient.h b/media/jni/tuner/FilterClient.h
index 20e5610..9e9b233 100644
--- a/media/jni/tuner/FilterClient.h
+++ b/media/jni/tuner/FilterClient.h
@@ -21,6 +21,7 @@
 #include <aidl/android/media/tv/tuner/BnTunerFilterCallback.h>
 #include <aidl/android/media/tv/tuner/ITunerFilter.h>
 #include <fmq/AidlMessageQueue.h>
+#include <utils/Mutex.h>
 
 #include "ClientHelper.h"
 #include "FilterClientCallback.h"
@@ -37,6 +38,7 @@
 using ::aidl::android::media::tv::tuner::BnTunerFilterCallback;
 using ::aidl::android::media::tv::tuner::ITunerFilter;
 using ::android::hardware::EventFlag;
+using ::android::Mutex;
 
 using namespace std;
 
@@ -179,6 +181,7 @@
     uint64_t mAvSharedMemSize;
     bool mIsMediaFilter;
     bool mIsPassthroughFilter;
+    Mutex mLock;
 };
 }  // namespace android
 
diff --git a/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml
index c206903..3f2b8ac 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml
@@ -43,4 +43,8 @@
     <color name="settingslib_accent_primary_variant">@android:color/system_accent1_300</color>
 
     <color name="settingslib_text_color_primary_device_default">@android:color/system_neutral1_50</color>
+
+    <color name="settingslib_text_color_secondary_device_default">@android:color/system_neutral2_200</color>
+
+    <color name="settingslib_text_color_preference_category_title">@android:color/system_accent1_100</color>
 </resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml
index 0401098..ec3c336 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml
@@ -65,4 +65,8 @@
     <color name="settingslib_background_device_default_light">@android:color/system_neutral1_50</color>
 
     <color name="settingslib_text_color_primary_device_default">@android:color/system_neutral1_900</color>
+
+    <color name="settingslib_text_color_secondary_device_default">@android:color/system_neutral2_700</color>
+
+    <color name="settingslib_text_color_preference_category_title">@android:color/system_accent1_600</color>
 </resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/dimens.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/dimens.xml
index 1c33f1a..11546c8 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v31/dimens.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/dimens.xml
@@ -20,4 +20,9 @@
     <dimen name="app_icon_min_width">52dp</dimen>
     <dimen name="settingslib_preferred_minimum_touch_target">48dp</dimen>
     <dimen name="settingslib_dialogCornerRadius">28dp</dimen>
+
+    <!-- Left padding of the preference -->
+    <dimen name="settingslib_listPreferredItemPaddingStart">24dp</dimen>
+    <!-- Right padding of the preference -->
+    <dimen name="settingslib_listPreferredItemPaddingEnd">24dp</dimen>
 </resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml
index 5800636..8e7226b 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml
@@ -17,13 +17,19 @@
 <resources>
     <style name="TextAppearance.PreferenceTitle.SettingsLib"
            parent="@android:style/TextAppearance.Material.Subhead">
+        <item name="android:textColor">@color/settingslib_text_color_primary_device_default</item>
         <item name="android:fontFamily">@string/settingslib_config_headlineFontFamily</item>
         <item name="android:textSize">20sp</item>
     </style>
 
+    <style name="TextAppearance.PreferenceSummary.SettingsLib"
+           parent="@android:style/TextAppearance.DeviceDefault.Small">
+        <item name="android:textColor">@color/settingslib_text_color_secondary_device_default</item>
+    </style>
+
     <style name="TextAppearance.CategoryTitle.SettingsLib"
            parent="@android:style/TextAppearance.DeviceDefault.Medium">
-        <item name="android:textColor">?android:attr/textColorPrimary</item>
+        <item name="android:textColor">@color/settingslib_text_color_preference_category_title</item>
         <item name="android:textSize">14sp</item>
     </style>
 
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml
index 6bf288b..7bf75bc 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml
@@ -19,8 +19,8 @@
     <!-- Only using in Settings application -->
     <style name="Theme.SettingsBase" parent="@android:style/Theme.DeviceDefault.Settings" >
         <item name="android:textAppearanceListItem">@style/TextAppearance.PreferenceTitle.SettingsLib</item>
-        <item name="android:listPreferredItemPaddingStart">24dp</item>
-        <item name="android:listPreferredItemPaddingLeft">24dp</item>
+        <item name="android:listPreferredItemPaddingStart">@dimen/settingslib_listPreferredItemPaddingStart</item>
+        <item name="android:listPreferredItemPaddingLeft">@dimen/settingslib_listPreferredItemPaddingStart</item>
         <item name="android:listPreferredItemPaddingEnd">16dp</item>
         <item name="android:listPreferredItemPaddingRight">16dp</item>
         <item name="preferenceTheme">@style/PreferenceTheme.SettingsLib</item>
diff --git a/packages/SettingsLib/SettingsTheme/res/values/config.xml b/packages/SettingsLib/SettingsTheme/res/values/config.xml
new file mode 100644
index 0000000..e73dcc0
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values/config.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright (C) 2022 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.
+  -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <bool name="settingslib_config_icon_space_reserved">true</bool>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values/dimens.xml b/packages/SettingsLib/SettingsTheme/res/values/dimens.xml
index 18af1f9..f7e0144 100644
--- a/packages/SettingsLib/SettingsTheme/res/values/dimens.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values/dimens.xml
@@ -21,4 +21,11 @@
     <dimen name="app_icon_min_width">56dp</dimen>
     <dimen name="two_target_min_width">72dp</dimen>
     <dimen name="settingslib_dialogCornerRadius">8dp</dimen>
+
+    <!-- Left padding of the preference -->
+    <dimen name="settingslib_listPreferredItemPaddingStart">16dp</dimen>
+    <!-- Right padding of the preference -->
+    <dimen name="settingslib_listPreferredItemPaddingEnd">16dp</dimen>
+    <!-- Icon size of the preference -->
+    <dimen name="settingslib_preferenceIconSize">24dp</dimen>
 </resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values/styles.xml b/packages/SettingsLib/SettingsTheme/res/values/styles.xml
new file mode 100644
index 0000000..aaab0f0
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values/styles.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright (C) 2022 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.
+  -->
+<resources>
+    <style name="TextAppearance.PreferenceTitle.SettingsLib"
+           parent="@android:style/TextAppearance.Material.Subhead">
+    </style>
+
+    <style name="TextAppearance.PreferenceSummary.SettingsLib"
+           parent="@style/PreferenceSummaryTextStyle">
+    </style>
+
+    <style name="TextAppearance.CategoryTitle.SettingsLib"
+           parent="@android:style/TextAppearance.DeviceDefault.Medium">
+    </style>
+</resources>
diff --git a/packages/SettingsLib/search/processor-src/com/android/settingslib/search/IndexableProcessor.java b/packages/SettingsLib/search/processor-src/com/android/settingslib/search/IndexableProcessor.java
index de45ea5..d3fe4a7 100644
--- a/packages/SettingsLib/search/processor-src/com/android/settingslib/search/IndexableProcessor.java
+++ b/packages/SettingsLib/search/processor-src/com/android/settingslib/search/IndexableProcessor.java
@@ -47,7 +47,7 @@
  * Annotation processor for {@link SearchIndexable} that generates {@link SearchIndexableResources}
  * subclasses.
  */
-@SupportedSourceVersion(SourceVersion.RELEASE_8)
+@SupportedSourceVersion(SourceVersion.RELEASE_9)
 @SupportedAnnotationTypes({"com.android.settingslib.search.SearchIndexable"})
 public class IndexableProcessor extends AbstractProcessor {
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEnterpriseRestrictionUtils.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEnterpriseRestrictionUtils.java
index 21a4ac6..fa4ae67 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEnterpriseRestrictionUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEnterpriseRestrictionUtils.java
@@ -60,6 +60,22 @@
         return true;
     }
 
+    /**
+     * Confirm Wi-Fi Config is allowed to add according to whether user restriction is set
+     *
+     * @param context A context
+     * @return whether the device is permitted to add new Wi-Fi config
+     */
+    public static boolean isAddWifiConfigAllowed(Context context) {
+        final UserManager userManager = context.getSystemService(UserManager.class);
+        final Bundle restrictions = userManager.getUserRestrictions();
+        if (isAtLeastT() && restrictions.getBoolean(UserManager.DISALLOW_ADD_WIFI_CONFIG)) {
+            Log.i(TAG, "Wi-Fi Add network isn't available due to user restriction.");
+            return false;
+        }
+        return true;
+    }
+
     @ChecksSdkIntAtLeast(api=Build.VERSION_CODES.TIRAMISU)
     private static boolean isAtLeastT() {
         return Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiEnterpriseRestrictionUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiEnterpriseRestrictionUtilsTest.java
index 3c339de..f6af09a 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiEnterpriseRestrictionUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiEnterpriseRestrictionUtilsTest.java
@@ -103,4 +103,30 @@
 
         assertThat(WifiEnterpriseRestrictionUtils.isWifiDirectAllowed(mContext)).isTrue();
     }
+
+    @Test
+    public void isAddWifiConfigAllowed_setSDKForS_shouldReturnTrue() {
+        ReflectionHelpers.setStaticField(Build.VERSION.class, "SDK_INT", Build.VERSION_CODES.S);
+        when(mBundle.getBoolean(UserManager.DISALLOW_ADD_WIFI_CONFIG)).thenReturn(true);
+
+        assertThat(WifiEnterpriseRestrictionUtils.isAddWifiConfigAllowed(mContext)).isTrue();
+    }
+
+    @Test
+    public void isAddWifiConfigAllowed_setSDKForTAndDisallowForRestriction_shouldReturnFalse() {
+        ReflectionHelpers.setStaticField(
+            Build.VERSION.class, "SDK_INT", Build.VERSION_CODES.TIRAMISU);
+        when(mBundle.getBoolean(UserManager.DISALLOW_ADD_WIFI_CONFIG)).thenReturn(true);
+
+        assertThat(WifiEnterpriseRestrictionUtils.isAddWifiConfigAllowed(mContext)).isFalse();
+    }
+
+    @Test
+    public void isAddWifiConfigAllowed_setSDKForTAndAllowForRestriction_shouldReturnTrue() {
+        ReflectionHelpers.setStaticField(
+            Build.VERSION.class, "SDK_INT", Build.VERSION_CODES.TIRAMISU);
+        when(mBundle.getBoolean(UserManager.DISALLOW_ADD_WIFI_CONFIG)).thenReturn(false);
+
+        assertThat(WifiEnterpriseRestrictionUtils.isAddWifiConfigAllowed(mContext)).isTrue();
+    }
 }
diff --git a/packages/SystemUI/res-keyguard/values/bools.xml b/packages/SystemUI/res-keyguard/values/bools.xml
index 2b83787..c5bf4ce 100644
--- a/packages/SystemUI/res-keyguard/values/bools.xml
+++ b/packages/SystemUI/res-keyguard/values/bools.xml
@@ -17,4 +17,5 @@
 <resources>
     <bool name="kg_show_ime_at_screen_on">true</bool>
     <bool name="kg_use_all_caps">true</bool>
+    <bool name="flag_active_unlock">false</bool>
 </resources>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 7b8f349..56517cc 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -294,6 +294,7 @@
     <string-array name="config_systemUIServiceComponents" translatable="false">
         <item>com.android.systemui.util.NotificationChannels</item>
         <item>com.android.systemui.keyguard.KeyguardViewMediator</item>
+        <item>com.android.keyguard.KeyguardBiometricLockoutLogger</item>
         <item>com.android.systemui.recents.Recents</item>
         <item>com.android.systemui.volume.VolumeUI</item>
         <item>com.android.systemui.statusbar.phone.StatusBar</item>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 2916c1c..7600eb1 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1165,7 +1165,7 @@
     <string name="wallet_lockscreen_settings_label">Lock screen settings</string>
 
     <!-- QR Code Scanner label, title [CHAR LIMIT=32] -->
-    <string name="qr_code_scanner_title">QR Code</string>
+    <string name="qr_code_scanner_title">QR code</string>
 
     <!-- QR Code Scanner description [CHAR LIMIT=NONE] -->
     <string name="qr_code_scanner_description">Tap to scan</string>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java
index 9808374..1d2caf9 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java
@@ -28,6 +28,8 @@
 import android.view.ViewRootImpl;
 import android.view.ViewTreeObserver;
 
+import androidx.annotation.VisibleForTesting;
+
 import java.io.PrintWriter;
 import java.util.concurrent.Executor;
 
@@ -60,6 +62,7 @@
     private final Rect mRegisteredSamplingBounds = new Rect();
     private final SamplingCallback mCallback;
     private final Executor mBackgroundExecutor;
+    private final SysuiCompositionSamplingListener mCompositionSamplingListener;
     private boolean mSamplingEnabled = false;
     private boolean mSamplingListenerRegistered = false;
 
@@ -91,9 +94,17 @@
 
     public RegionSamplingHelper(View sampledView, SamplingCallback samplingCallback,
             Executor backgroundExecutor) {
+        this(sampledView, samplingCallback, sampledView.getContext().getMainExecutor(),
+                backgroundExecutor, new SysuiCompositionSamplingListener());
+    }
+
+    @VisibleForTesting
+    RegionSamplingHelper(View sampledView, SamplingCallback samplingCallback,
+            Executor mainExecutor, Executor backgroundExecutor,
+            SysuiCompositionSamplingListener compositionSamplingListener) {
         mBackgroundExecutor = backgroundExecutor;
-        mSamplingListener = new CompositionSamplingListener(
-                sampledView.getContext().getMainExecutor()) {
+        mCompositionSamplingListener = compositionSamplingListener;
+        mSamplingListener = new CompositionSamplingListener(mainExecutor) {
             @Override
             public void onSampleCollected(float medianLuma) {
                 if (mSamplingEnabled) {
@@ -136,7 +147,7 @@
 
     public void stopAndDestroy() {
         stop();
-        mSamplingListener.destroy();
+        mBackgroundExecutor.execute(mSamplingListener::destroy);
         mIsDestroyed = true;
     }
 
@@ -189,13 +200,12 @@
                 // We only want to re-register if something actually changed
                 unregisterSamplingListener();
                 mSamplingListenerRegistered = true;
-                SurfaceControl wrappedStopLayer = stopLayerControl == null
-                        ? null : new SurfaceControl(stopLayerControl, "regionSampling");
+                SurfaceControl wrappedStopLayer = wrap(stopLayerControl);
                 mBackgroundExecutor.execute(() -> {
                     if (wrappedStopLayer != null && !wrappedStopLayer.isValid()) {
                         return;
                     }
-                    CompositionSamplingListener.register(mSamplingListener, DEFAULT_DISPLAY,
+                    mCompositionSamplingListener.register(mSamplingListener, DEFAULT_DISPLAY,
                             wrappedStopLayer, mSamplingRequestBounds);
                 });
                 mRegisteredSamplingBounds.set(mSamplingRequestBounds);
@@ -208,14 +218,21 @@
         }
     }
 
+    @VisibleForTesting
+    protected SurfaceControl wrap(SurfaceControl stopLayerControl) {
+        return stopLayerControl == null ? null : new SurfaceControl(stopLayerControl,
+                "regionSampling");
+    }
+
     private void unregisterSamplingListener() {
         if (mSamplingListenerRegistered) {
             mSamplingListenerRegistered = false;
             SurfaceControl wrappedStopLayer = mWrappedStopLayer;
             mRegisteredStopLayer = null;
+            mWrappedStopLayer = null;
             mRegisteredSamplingBounds.setEmpty();
             mBackgroundExecutor.execute(() -> {
-                CompositionSamplingListener.unregister(mSamplingListener);
+                mCompositionSamplingListener.unregister(mSamplingListener);
                 if (wrappedStopLayer != null && wrappedStopLayer.isValid()) {
                     wrappedStopLayer.release();
                 }
@@ -299,4 +316,19 @@
             return true;
         }
     }
+
+    @VisibleForTesting
+    public static class SysuiCompositionSamplingListener {
+        public void register(CompositionSamplingListener listener,
+                int displayId, SurfaceControl stopLayer, Rect samplingArea) {
+            CompositionSamplingListener.register(listener, displayId, stopLayer, samplingArea);
+        }
+
+        /**
+         * Unregisters a sampling listener.
+         */
+        public void unregister(CompositionSamplingListener listener) {
+            CompositionSamplingListener.unregister(listener);
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardBiometricLockoutLogger.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardBiometricLockoutLogger.kt
new file mode 100644
index 0000000..214b284
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardBiometricLockoutLogger.kt
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2022 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.keyguard
+
+import android.content.Context
+import android.hardware.biometrics.BiometricSourceType
+import com.android.internal.annotations.VisibleForTesting
+import com.android.internal.logging.UiEvent
+import com.android.internal.logging.UiEventLogger
+import com.android.internal.widget.LockPatternUtils
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE
+import com.android.keyguard.KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dump.DumpManager
+import java.io.FileDescriptor
+import java.io.PrintWriter
+import javax.inject.Inject
+
+/**
+ * Logs events when primary authentication requirements change. Primary authentication is considered
+ * authentication using pin/pattern/password input.
+ *
+ * See [PrimaryAuthRequiredEvent] for all the events and their descriptions.
+ */
+@SysUISingleton
+class KeyguardBiometricLockoutLogger @Inject constructor(
+    context: Context?,
+    private val uiEventLogger: UiEventLogger,
+    private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+    private val dumpManager: DumpManager
+) : CoreStartable(context) {
+    private var fingerprintLockedOut = false
+    private var faceLockedOut = false
+    private var encryptedOrLockdown = false
+    private var unattendedUpdate = false
+    private var timeout = false
+
+    override fun start() {
+        dumpManager.registerDumpable(this)
+        mKeyguardUpdateMonitorCallback.onStrongAuthStateChanged(
+                KeyguardUpdateMonitor.getCurrentUser())
+        keyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback)
+    }
+
+    private val mKeyguardUpdateMonitorCallback: KeyguardUpdateMonitorCallback =
+            object : KeyguardUpdateMonitorCallback() {
+        override fun onLockedOutStateChanged(biometricSourceType: BiometricSourceType) {
+            if (biometricSourceType == BiometricSourceType.FINGERPRINT) {
+                val lockedOut = keyguardUpdateMonitor.isFingerprintLockedOut
+                if (lockedOut && !fingerprintLockedOut) {
+                    uiEventLogger.log(
+                            PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_FINGERPRINT_LOCKED_OUT)
+                } else if (!lockedOut && fingerprintLockedOut) {
+                    uiEventLogger.log(
+                            PrimaryAuthRequiredEvent
+                                    .PRIMARY_AUTH_REQUIRED_FINGERPRINT_LOCKED_OUT_RESET)
+                }
+                fingerprintLockedOut = lockedOut
+            } else if (biometricSourceType == BiometricSourceType.FACE) {
+                val lockedOut = keyguardUpdateMonitor.isFaceLockedOut
+                if (lockedOut && !faceLockedOut) {
+                    uiEventLogger.log(
+                            PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_FACE_LOCKED_OUT)
+                } else if (!lockedOut && faceLockedOut) {
+                    uiEventLogger.log(
+                            PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_FACE_LOCKED_OUT_RESET)
+                }
+                faceLockedOut = lockedOut
+            }
+        }
+
+        override fun onStrongAuthStateChanged(userId: Int) {
+            if (userId != KeyguardUpdateMonitor.getCurrentUser()) {
+                return
+            }
+            val strongAuthFlags = keyguardUpdateMonitor.strongAuthTracker
+                    .getStrongAuthForUser(userId)
+
+            val newEncryptedOrLockdown = keyguardUpdateMonitor.isEncryptedOrLockdown(userId)
+            if (newEncryptedOrLockdown && !encryptedOrLockdown) {
+                uiEventLogger.log(
+                        PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_ENCRYPTED_OR_LOCKDOWN)
+            }
+            encryptedOrLockdown = newEncryptedOrLockdown
+
+            val newUnattendedUpdate = isUnattendedUpdate(strongAuthFlags)
+            if (newUnattendedUpdate && !unattendedUpdate) {
+                uiEventLogger.log(PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_UNATTENDED_UPDATE)
+            }
+            unattendedUpdate = newUnattendedUpdate
+
+            val newTimeout = isStrongAuthTimeout(strongAuthFlags)
+            if (newTimeout && !timeout) {
+                uiEventLogger.log(PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_TIMEOUT)
+            }
+            timeout = newTimeout
+        }
+    }
+
+    private fun isUnattendedUpdate(
+        @LockPatternUtils.StrongAuthTracker.StrongAuthFlags flags: Int
+    ) = containsFlag(flags, STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE)
+
+    private fun isStrongAuthTimeout(
+        @LockPatternUtils.StrongAuthTracker.StrongAuthFlags flags: Int
+    ) = containsFlag(flags, STRONG_AUTH_REQUIRED_AFTER_TIMEOUT) ||
+            containsFlag(flags, STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT)
+
+    override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<String>) {
+        pw.println("  mFingerprintLockedOut=$fingerprintLockedOut")
+        pw.println("  mFaceLockedOut=$faceLockedOut")
+        pw.println("  mIsEncryptedOrLockdown=$encryptedOrLockdown")
+        pw.println("  mIsUnattendedUpdate=$unattendedUpdate")
+        pw.println("  mIsTimeout=$timeout")
+    }
+
+    /**
+     * Events pertaining to whether primary authentication (pin/pattern/password input) is required
+     * for device entry.
+     */
+    @VisibleForTesting
+    enum class PrimaryAuthRequiredEvent(private val mId: Int) : UiEventLogger.UiEventEnum {
+        @UiEvent(doc = "Fingerprint cannot be used to authenticate for device entry. This" +
+                "can persist until the next primary auth or may timeout.")
+        PRIMARY_AUTH_REQUIRED_FINGERPRINT_LOCKED_OUT(924),
+
+        @UiEvent(doc = "Fingerprint can be used to authenticate for device entry.")
+        PRIMARY_AUTH_REQUIRED_FINGERPRINT_LOCKED_OUT_RESET(925),
+
+        @UiEvent(doc = "Face cannot be used to authenticate for device entry.")
+        PRIMARY_AUTH_REQUIRED_FACE_LOCKED_OUT(926),
+
+        @UiEvent(doc = "Face can be used to authenticate for device entry.")
+        PRIMARY_AUTH_REQUIRED_FACE_LOCKED_OUT_RESET(927),
+
+        @UiEvent(doc = "Device is encrypted (ie: after reboot) or device is locked down by DPM " +
+                "or a manual user lockdown.")
+        PRIMARY_AUTH_REQUIRED_ENCRYPTED_OR_LOCKDOWN(928),
+
+        @UiEvent(doc = "Primary authentication is required because it hasn't been used for a " +
+                "time required by a device admin or because primary auth hasn't been used for a " +
+                "time after a non-strong biometric (weak or convenience) is used to unlock the " +
+                "device.")
+        PRIMARY_AUTH_REQUIRED_TIMEOUT(929),
+
+        @UiEvent(doc = "Strong authentication is required to prepare for unattended upgrade.")
+        PRIMARY_AUTH_REQUIRED_UNATTENDED_UPDATE(931);
+
+        override fun getId(): Int {
+            return mId
+        }
+    }
+
+    companion object {
+        private fun containsFlag(strongAuthFlags: Int, flagCheck: Int): Boolean {
+            return strongAuthFlags and flagCheck != 0
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
index 03f04d3..36fe5ba 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
@@ -64,3 +64,19 @@
     val secureCameraLaunched: Boolean,
     val switchingUser: Boolean
 ) : KeyguardListenModel()
+/**
+ * Verbose debug information associated with [KeyguardUpdateMonitor.shouldTriggerActiveUnlock].
+ */
+data class KeyguardActiveUnlockModel(
+    @CurrentTimeMillisLong override val timeMillis: Long,
+    override val userId: Int,
+    override val listening: Boolean,
+    // keep sorted
+    val authInterruptActive: Boolean,
+    val encryptedOrTimedOut: Boolean,
+    val fpLockout: Boolean,
+    val lockDown: Boolean,
+    val switchingUser: Boolean,
+    val triggerActiveUnlockForAssistant: Boolean,
+    val userCanDismissLockScreen: Boolean
+) : KeyguardListenModel()
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardListenQueue.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardListenQueue.kt
index f13a59a..210f5e7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardListenQueue.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardListenQueue.kt
@@ -32,15 +32,17 @@
 ) {
     private val faceQueue = ArrayDeque<KeyguardFaceListenModel>()
     private val fingerprintQueue = ArrayDeque<KeyguardFingerprintListenModel>()
+    private val activeUnlockQueue = ArrayDeque<KeyguardActiveUnlockModel>()
 
     @get:VisibleForTesting val models: List<KeyguardListenModel>
-        get() = faceQueue + fingerprintQueue
+        get() = faceQueue + fingerprintQueue + activeUnlockQueue
 
     /** Push a [model] to the queue (will be logged until the queue exceeds [sizePerModality]). */
     fun add(model: KeyguardListenModel) {
         val queue = when (model) {
             is KeyguardFaceListenModel -> faceQueue.apply { add(model) }
             is KeyguardFingerprintListenModel -> fingerprintQueue.apply { add(model) }
+            is KeyguardActiveUnlockModel -> activeUnlockQueue.apply { add(model) }
         }
 
         if (queue.size > sizePerModality) {
@@ -63,5 +65,9 @@
         for (model in fingerprintQueue) {
             writer.println(stringify(model))
         }
+        writer.println("  Active unlock triggers (last ${activeUnlockQueue.size} calls):")
+        for (model in activeUnlockQueue) {
+            writer.println(stringify(model))
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 98721fd..5276679 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -37,6 +37,8 @@
 import android.app.ActivityTaskManager;
 import android.app.ActivityTaskManager.RootTaskInfo;
 import android.app.AlarmManager;
+import android.app.Notification;
+import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.app.UserSwitchObserver;
 import android.app.admin.DevicePolicyManager;
@@ -102,6 +104,8 @@
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.shared.system.TaskStackChangeListeners;
@@ -109,6 +113,7 @@
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.telephony.TelephonyListenerManager;
 import com.android.systemui.util.Assert;
+import com.android.systemui.util.NotificationChannels;
 import com.android.systemui.util.RingerModeTracker;
 
 import com.google.android.collect.Lists;
@@ -143,8 +148,10 @@
     private static final boolean DEBUG_SIM_STATES = KeyguardConstants.DEBUG_SIM_STATES;
     private static final boolean DEBUG_FACE = Build.IS_DEBUGGABLE;
     private static final boolean DEBUG_FINGERPRINT = Build.IS_DEBUGGABLE;
+    private static final boolean DEBUG_ACTIVE_UNLOCK = Build.IS_DEBUGGABLE;
     private static final boolean DEBUG_SPEW = false;
     private static final int BIOMETRIC_LOCKOUT_RESET_DELAY_MS = 600;
+    private int mNumActiveUnlockTriggers = 0;
 
     private static final String ACTION_FACE_UNLOCK_STARTED
             = "com.android.facelock.FACE_UNLOCK_STARTED";
@@ -183,7 +190,6 @@
     private static final int MSG_USER_STOPPED = 340;
     private static final int MSG_USER_REMOVED = 341;
     private static final int MSG_KEYGUARD_GOING_AWAY = 342;
-    private static final int MSG_LOCK_SCREEN_MODE = 343;
     private static final int MSG_TIME_FORMAT_UPDATE = 344;
     private static final int MSG_REQUIRE_NFC_UNLOCK = 345;
 
@@ -221,7 +227,6 @@
     private static final int BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED = -1;
     public static final int BIOMETRIC_HELP_FACE_NOT_RECOGNIZED = -2;
 
-    private static final int DEFAULT_CHARGING_VOLTAGE_MICRO_VOLT = 5000000;
     /**
      * If no cancel signal has been received after this amount of time, set the biometric running
      * state to stopped to allow Keyguard to retry authentication.
@@ -231,7 +236,6 @@
     private static final ComponentName FALLBACK_HOME_COMPONENT = new ComponentName(
             "com.android.settings", "com.android.settings.FallbackHome");
 
-
     /**
      * If true, the system is in the half-boot-to-decryption-screen state.
      * Prudently disable lockscreen.
@@ -334,6 +338,7 @@
     private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
     private final Executor mBackgroundExecutor;
     private SensorPrivacyManager mSensorPrivacyManager;
+    private FeatureFlags mFeatureFlags;
     private int mFaceAuthUserId;
 
     /**
@@ -1250,7 +1255,11 @@
                 STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
     }
 
-    private boolean isEncryptedOrLockdown(int userId) {
+    /**
+     * Returns true if primary authentication is required for the given user due to lockdown
+     * or encryption after reboot.
+     */
+    public boolean isEncryptedOrLockdown(int userId) {
         final int strongAuth = mStrongAuthTracker.getStrongAuthForUser(userId);
         final boolean isLockDown =
                 containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW)
@@ -1311,6 +1320,9 @@
     void setAssistantVisible(boolean assistantVisible) {
         mAssistantVisible = assistantVisible;
         updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE);
+        if (mAssistantVisible) {
+            requestActiveUnlock();
+        }
     }
 
     static class DisplayClientState {
@@ -1650,6 +1662,7 @@
         Trace.beginSection("KeyguardUpdateMonitor#handleStartedWakingUp");
         Assert.isMainThread();
         updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE);
+        requestActiveUnlock();
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
@@ -1777,7 +1790,8 @@
             AuthController authController,
             TelephonyListenerManager telephonyListenerManager,
             InteractionJankMonitor interactionJankMonitor,
-            LatencyTracker latencyTracker) {
+            LatencyTracker latencyTracker,
+            FeatureFlags featureFlags) {
         mContext = context;
         mSubscriptionManager = SubscriptionManager.from(context);
         mTelephonyListenerManager = telephonyListenerManager;
@@ -1795,6 +1809,7 @@
         mAuthController = authController;
         dumpManager.registerDumpable(getClass().getName(), this);
         mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
+        mFeatureFlags = featureFlags;
 
         mHandler = new Handler(mainLooper) {
             @Override
@@ -2180,6 +2195,7 @@
         }
         mAuthInterruptActive = active;
         updateFaceListeningState(BIOMETRIC_ACTION_UPDATE);
+        requestActiveUnlock();
     }
 
     /**
@@ -2228,6 +2244,97 @@
         }
     }
 
+    /**
+     * Attempts to trigger active unlock.
+     */
+    public void requestActiveUnlock() {
+        // If this message exists, FP has already authenticated, so wait until that is handled
+        if (mHandler.hasMessages(MSG_BIOMETRIC_AUTHENTICATION_CONTINUE)) {
+            return;
+        }
+
+        if (shouldTriggerActiveUnlock() && mFeatureFlags.isEnabled(Flags.ACTIVE_UNLOCK)) {
+            // TODO (b/192405661): call new TrustManager API
+            mNumActiveUnlockTriggers++;
+            Log.d("ActiveUnlock", "would have triggered times=" + mNumActiveUnlockTriggers);
+            showActiveUnlockNotification(mNumActiveUnlockTriggers);
+        }
+    }
+
+    /**
+     * TODO (b/192405661): Only for testing. Remove before release.
+     */
+    private void showActiveUnlockNotification(int times) {
+        final String message = "Active unlock triggered "  + times + " times.";
+        final Notification.Builder nb =
+                new Notification.Builder(mContext, NotificationChannels.GENERAL)
+                        .setSmallIcon(R.drawable.ic_volume_ringer)
+                        .setContentTitle(message)
+                        .setStyle(new Notification.BigTextStyle().bigText(message));
+        mContext.getSystemService(NotificationManager.class).notifyAsUser(
+                "active_unlock",
+                0,
+                nb.build(),
+                UserHandle.ALL);
+    }
+
+    private boolean shouldTriggerActiveUnlock() {
+        // TODO: check if active unlock is ENABLED / AVAILABLE
+
+        // Triggers:
+        final boolean triggerActiveUnlockForAssistant = shouldTriggerActiveUnlockForAssistant();
+        final boolean awakeKeyguard = mKeyguardIsVisible && mDeviceInteractive && !mGoingToSleep
+                && mStatusBarState != StatusBarState.SHADE_LOCKED;
+
+        // Gates:
+        final int user = getCurrentUser();
+
+        // No need to trigger active unlock if we're already unlocked or don't have
+        // pin/pattern/password setup
+        final boolean userCanDismissLockScreen = getUserCanSkipBouncer(user)
+                || !mLockPatternUtils.isSecure(user);
+
+        // Don't trigger active unlock if fp is locked out TODO: confirm this one
+        final boolean fpLockedout = mFingerprintLockedOut || mFingerprintLockedOutPermanent;
+
+        // Don't trigger active unlock if primary auth is required
+        final int strongAuth = mStrongAuthTracker.getStrongAuthForUser(user);
+        final boolean isLockDown =
+                containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW)
+                        || containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
+        final boolean isEncryptedOrTimedOut =
+                containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_BOOT)
+                        || containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_TIMEOUT);
+
+        final boolean shouldTriggerActiveUnlock =
+                (mAuthInterruptActive || triggerActiveUnlockForAssistant || awakeKeyguard)
+                        && !mSwitchingUser
+                        && !userCanDismissLockScreen
+                        && !fpLockedout
+                        && !isLockDown
+                        && !isEncryptedOrTimedOut
+                        && !mKeyguardGoingAway
+                        && !mSecureCameraLaunched;
+
+        // Aggregate relevant fields for debug logging.
+        if (DEBUG_ACTIVE_UNLOCK || DEBUG_SPEW) {
+            maybeLogListenerModelData(
+                    new KeyguardActiveUnlockModel(
+                            System.currentTimeMillis(),
+                            user,
+                            shouldTriggerActiveUnlock,
+                            mAuthInterruptActive,
+                            isEncryptedOrTimedOut,
+                            fpLockedout,
+                            isLockDown,
+                            mSwitchingUser,
+                            triggerActiveUnlockForAssistant,
+                            userCanDismissLockScreen));
+        }
+
+        return shouldTriggerActiveUnlock;
+    }
+
     private boolean shouldListenForFingerprintAssistant() {
         BiometricAuthenticated fingerprint = mUserFingerprintAuthenticated.get(getCurrentUser());
         return mAssistantVisible && mKeyguardOccluded
@@ -2242,6 +2349,11 @@
                 && !mUserHasTrust.get(getCurrentUser(), false);
     }
 
+    private boolean shouldTriggerActiveUnlockForAssistant() {
+        return mAssistantVisible && mKeyguardOccluded
+                && !mUserHasTrust.get(getCurrentUser(), false);
+    }
+
     @VisibleForTesting
     protected boolean shouldListenForFingerprint(boolean isUdfps) {
         final int user = getCurrentUser();
@@ -2406,6 +2518,13 @@
             Log.v(TAG, model.toString());
         }
 
+        if (DEBUG_ACTIVE_UNLOCK
+                && model instanceof KeyguardActiveUnlockModel
+                && model.getListening()) {
+            mListenModels.add(model);
+            return;
+        }
+
         // Add model data to the historical buffer.
         final boolean notYetRunning =
                 (DEBUG_FACE
@@ -2514,6 +2633,10 @@
         return mFingerprintLockedOut || mFingerprintLockedOutPermanent;
     }
 
+    public boolean isFaceLockedOut() {
+        return mFaceLockedOutPermanent;
+    }
+
     /**
      * If biometrics hardware is available, not disabled, and user has enrolled templates.
      * This does NOT check if the device is encrypted or in lockdown.
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
index 9dddbb1..c0da57f 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.dagger;
 
+import com.android.keyguard.KeyguardBiometricLockoutLogger;
 import com.android.systemui.CoreStartable;
 import com.android.systemui.LatencyTester;
 import com.android.systemui.ScreenDecorations;
@@ -90,6 +91,13 @@
     @ClassKey(KeyguardViewMediator.class)
     public abstract CoreStartable bindKeyguardViewMediator(KeyguardViewMediator sysui);
 
+    /** Inject into KeyguardBiometricLockoutLogger. */
+    @Binds
+    @IntoMap
+    @ClassKey(KeyguardBiometricLockoutLogger.class)
+    public abstract CoreStartable bindKeyguardBiometricLockoutLogger(
+            KeyguardBiometricLockoutLogger sysui);
+
     /** Inject into LatencyTests. */
     @Binds
     @IntoMap
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index 4be819a..5d6c2a2 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -74,6 +74,9 @@
     public static final ResourceBooleanFlag BOUNCER_USER_SWITCHER =
             new ResourceBooleanFlag(204, R.bool.config_enableBouncerUserSwitcher);
 
+    public static final ResourceBooleanFlag ACTIVE_UNLOCK =
+            new ResourceBooleanFlag(205, R.bool.flag_active_unlock);
+
     /***************************************/
     // 300 - power menu
     public static final BooleanFlag POWER_MENU_LITE =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index bfa4a24..dee1b33 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -11,7 +11,7 @@
  * 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
+ * limitations under the License.
  */
 
 package com.android.systemui.statusbar.phone;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
index ec2d036..571c10b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
@@ -54,6 +54,7 @@
             isListening = false
             updateListeningState()
             keyguardUpdateMonitor.requestFaceAuth(true)
+            keyguardUpdateMonitor.requestActiveUnlock()
         }
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt
new file mode 100644
index 0000000..6bc6505
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2022 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.keyguard
+
+import android.hardware.biometrics.BiometricSourceType
+import org.mockito.Mockito.verify
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.UiEventLogger
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class KeyguardBiometricLockoutLoggerTest : SysuiTestCase() {
+    @Mock
+    lateinit var uiEventLogger: UiEventLogger
+    @Mock
+    lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+    @Mock
+    lateinit var dumpManager: DumpManager
+    @Mock
+    lateinit var strongAuthTracker: KeyguardUpdateMonitor.StrongAuthTracker
+
+    @Captor
+    lateinit var updateMonitorCallbackCaptor: ArgumentCaptor<KeyguardUpdateMonitorCallback>
+    lateinit var updateMonitorCallback: KeyguardUpdateMonitorCallback
+
+    lateinit var keyguardBiometricLockoutLogger: KeyguardBiometricLockoutLogger
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        whenever(keyguardUpdateMonitor.strongAuthTracker).thenReturn(strongAuthTracker)
+        keyguardBiometricLockoutLogger = KeyguardBiometricLockoutLogger(
+                mContext,
+                uiEventLogger,
+                keyguardUpdateMonitor,
+                dumpManager)
+    }
+
+    @Test
+    fun test_logsOnStart() {
+        // GIVEN is encrypted / lockdown before start
+        whenever(keyguardUpdateMonitor.isEncryptedOrLockdown(anyInt()))
+                .thenReturn(true)
+
+        // WHEN start
+        keyguardBiometricLockoutLogger.start()
+
+        // THEN encrypted / lockdown state is logged
+        verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent
+                .PRIMARY_AUTH_REQUIRED_ENCRYPTED_OR_LOCKDOWN)
+    }
+
+    @Test
+    fun test_logTimeoutChange() {
+        keyguardBiometricLockoutLogger.start()
+        captureUpdateMonitorCallback()
+
+        // GIVEN primary auth required b/c timeout
+        whenever(strongAuthTracker.getStrongAuthForUser(anyInt()))
+                .thenReturn(STRONG_AUTH_REQUIRED_AFTER_TIMEOUT)
+
+        // WHEN primary auth requirement changes
+        updateMonitorCallback.onStrongAuthStateChanged(0)
+
+        // THEN primary auth required state is logged
+        verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent
+                .PRIMARY_AUTH_REQUIRED_TIMEOUT)
+    }
+
+    @Test
+    fun test_logUnattendedUpdate() {
+        keyguardBiometricLockoutLogger.start()
+        captureUpdateMonitorCallback()
+
+        // GIVEN primary auth required b/c unattended update
+        whenever(strongAuthTracker.getStrongAuthForUser(anyInt()))
+                .thenReturn(STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE)
+
+        // WHEN primary auth requirement changes
+        updateMonitorCallback.onStrongAuthStateChanged(0)
+
+        // THEN primary auth required state is logged
+        verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent
+                .PRIMARY_AUTH_REQUIRED_UNATTENDED_UPDATE)
+    }
+
+    @Test
+    fun test_logMultipleChanges() {
+        keyguardBiometricLockoutLogger.start()
+        captureUpdateMonitorCallback()
+
+        // GIVEN primary auth required b/c timeout
+        whenever(strongAuthTracker.getStrongAuthForUser(anyInt()))
+                .thenReturn(STRONG_AUTH_REQUIRED_AFTER_TIMEOUT
+                        or STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE)
+
+        // WHEN primary auth requirement changes
+        updateMonitorCallback.onStrongAuthStateChanged(0)
+
+        // THEN primary auth required state is logged with all the reasons
+        verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent
+                .PRIMARY_AUTH_REQUIRED_TIMEOUT)
+        verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent
+                .PRIMARY_AUTH_REQUIRED_UNATTENDED_UPDATE)
+
+        // WHEN onStrongAuthStateChanged is called again
+        updateMonitorCallback.onStrongAuthStateChanged(0)
+
+        // THEN no more events are sent since there haven't been any changes
+        verifyNoMoreInteractions(uiEventLogger)
+    }
+
+    @Test
+    fun test_logFaceLockout() {
+        keyguardBiometricLockoutLogger.start()
+        captureUpdateMonitorCallback()
+
+        // GIVEN primary auth required b/c face lock
+        whenever(keyguardUpdateMonitor.isFaceLockedOut).thenReturn(true)
+
+        // WHEN lockout state changes
+        updateMonitorCallback.onLockedOutStateChanged(BiometricSourceType.FACE)
+
+        // THEN primary auth required state is logged
+        verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent
+                .PRIMARY_AUTH_REQUIRED_FACE_LOCKED_OUT)
+
+        // WHEN face lockout is reset
+        whenever(keyguardUpdateMonitor.isFaceLockedOut).thenReturn(false)
+        updateMonitorCallback.onLockedOutStateChanged(BiometricSourceType.FACE)
+
+        // THEN primary auth required state is logged
+        verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent
+                .PRIMARY_AUTH_REQUIRED_FACE_LOCKED_OUT_RESET)
+    }
+
+    @Test
+    fun test_logFingerprintLockout() {
+        keyguardBiometricLockoutLogger.start()
+        captureUpdateMonitorCallback()
+
+        // GIVEN primary auth required b/c fingerprint lock
+        whenever(keyguardUpdateMonitor.isFingerprintLockedOut).thenReturn(true)
+
+        // WHEN lockout state changes
+        updateMonitorCallback.onLockedOutStateChanged(BiometricSourceType.FINGERPRINT)
+
+        // THEN primary auth required state is logged
+        verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent
+                .PRIMARY_AUTH_REQUIRED_FINGERPRINT_LOCKED_OUT)
+
+        // WHEN fingerprint lockout is reset
+        whenever(keyguardUpdateMonitor.isFingerprintLockedOut).thenReturn(false)
+        updateMonitorCallback.onLockedOutStateChanged(BiometricSourceType.FINGERPRINT)
+
+        // THEN primary auth required state is logged
+        verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent
+                .PRIMARY_AUTH_REQUIRED_FINGERPRINT_LOCKED_OUT_RESET)
+    }
+
+    fun captureUpdateMonitorCallback() {
+        verify(keyguardUpdateMonitor).registerCallback(updateMonitorCallbackCaptor.capture())
+        updateMonitorCallback = updateMonitorCallbackCaptor.value
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 70792cf..7266e41 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -88,6 +88,7 @@
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
@@ -173,6 +174,8 @@
     private InteractionJankMonitor mInteractionJankMonitor;
     @Mock
     private LatencyTracker mLatencyTracker;
+    @Mock
+    private FeatureFlags mFeatureFlags;
     @Captor
     private ArgumentCaptor<StatusBarStateController.StateListener> mStatusBarStateListenerCaptor;
     // Direct executor
@@ -1105,7 +1108,7 @@
                     mRingerModeTracker, mBackgroundExecutor, mMainExecutor,
                     mStatusBarStateController, mLockPatternUtils,
                     mAuthController, mTelephonyListenerManager,
-                    mInteractionJankMonitor, mLatencyTracker);
+                    mInteractionJankMonitor, mLatencyTracker, mFeatureFlags);
             setStrongAuthTracker(KeyguardUpdateMonitorTest.this.mStrongAuthTracker);
         }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/navigationbar/RegionSamplingHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/navigationbar/RegionSamplingHelperTest.kt
new file mode 100644
index 0000000..8bc438b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/navigationbar/RegionSamplingHelperTest.kt
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shared.navigationbar
+import android.graphics.Rect
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.view.SurfaceControl
+import android.view.View
+import android.view.ViewRootImpl
+import androidx.concurrent.futures.DirectExecutor
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mock
+import org.mockito.Mockito.*
+import org.mockito.junit.MockitoJUnit
+import org.mockito.Mockito.`when` as whenever
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+@RunWithLooper
+class RegionSamplingHelperTest : SysuiTestCase() {
+
+    @Mock
+    lateinit var sampledView: View
+    @Mock
+    lateinit var samplingCallback: RegionSamplingHelper.SamplingCallback
+    @Mock
+    lateinit var compositionListener: RegionSamplingHelper.SysuiCompositionSamplingListener
+    @Mock
+    lateinit var viewRootImpl: ViewRootImpl
+    @Mock
+    lateinit var surfaceControl: SurfaceControl
+    @Mock
+    lateinit var wrappedSurfaceControl: SurfaceControl
+    @JvmField @Rule
+    var rule = MockitoJUnit.rule()
+    lateinit var regionSamplingHelper: RegionSamplingHelper
+
+    @Before
+    fun setup() {
+        whenever(sampledView.isAttachedToWindow).thenReturn(true)
+        whenever(sampledView.viewRootImpl).thenReturn(viewRootImpl)
+        whenever(viewRootImpl.surfaceControl).thenReturn(surfaceControl)
+        whenever(surfaceControl.isValid).thenReturn(true)
+        whenever(wrappedSurfaceControl.isValid).thenReturn(true)
+        whenever(samplingCallback.isSamplingEnabled).thenReturn(true)
+        regionSamplingHelper = object : RegionSamplingHelper(sampledView, samplingCallback,
+                DirectExecutor.INSTANCE, DirectExecutor.INSTANCE, compositionListener) {
+            override fun wrap(stopLayerControl: SurfaceControl?): SurfaceControl {
+                return wrappedSurfaceControl
+            }
+        }
+        regionSamplingHelper.setWindowVisible(true)
+    }
+
+    @Test
+    fun testStart_register() {
+        regionSamplingHelper.start(Rect(0, 0, 100, 100))
+        verify(compositionListener).register(any(), anyInt(), eq(wrappedSurfaceControl), any())
+    }
+
+    @Test
+    fun testStart_unregister() {
+        regionSamplingHelper.start(Rect(0, 0, 100, 100))
+        regionSamplingHelper.setWindowVisible(false)
+        verify(compositionListener).unregister(any())
+    }
+
+    @Test
+    fun testStart_hasBlur_neverRegisters() {
+        regionSamplingHelper.setWindowHasBlurs(true)
+        regionSamplingHelper.start(Rect(0, 0, 100, 100))
+        verify(compositionListener, never())
+                .register(any(), anyInt(), eq(wrappedSurfaceControl), any())
+    }
+
+    @Test
+    fun testStart_stopAndDestroy() {
+        regionSamplingHelper.start(Rect(0, 0, 100, 100))
+        regionSamplingHelper.stopAndDestroy()
+        verify(compositionListener).unregister(any())
+    }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index bc8da84..262933d 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -45,6 +45,7 @@
 import android.bluetooth.IBluetoothManagerCallback;
 import android.bluetooth.IBluetoothProfileServiceConnection;
 import android.bluetooth.IBluetoothStateChangeCallback;
+import android.bluetooth.IBluetoothLeCallControl;
 import android.content.ActivityNotFoundException;
 import android.content.AttributionSource;
 import android.content.BroadcastReceiver;
@@ -1323,11 +1324,15 @@
                             + bluetoothProfile);
                 }
 
-                if (bluetoothProfile != BluetoothProfile.HEADSET) {
+                Intent intent;
+                if (bluetoothProfile == BluetoothProfile.HEADSET) {
+                    intent = new Intent(IBluetoothHeadset.class.getName());
+                } else if (bluetoothProfile== BluetoothProfile.LE_CALL_CONTROL) {
+                    intent = new Intent(IBluetoothLeCallControl.class.getName());
+                } else {
                     return false;
                 }
 
-                Intent intent = new Intent(IBluetoothHeadset.class.getName());
                 psc = new ProfileServiceConnections(intent);
                 if (!psc.bindService()) {
                     return false;
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 9b731d5..b3e46cd 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -174,6 +174,9 @@
     boolean mFgsNotificationWasDeferred;
     // FGS notification was shown before the FGS finishes, or it wasn't deferred in the first place.
     boolean mFgsNotificationShown;
+    // Whether FGS package has permissions to show notifications.
+    // TODO(b/194833441): Output this field to logs in ActiveServices#logFGSStateChangeLocked.
+    boolean mFgsHasNotificationPermission;
 
     // allow the service becomes foreground service? Service started from background may not be
     // allowed to become a foreground service.
@@ -968,6 +971,9 @@
                     if (nm == null) {
                         return;
                     }
+                    // Record whether the package has permission to notify the user
+                    mFgsHasNotificationPermission = nm.areNotificationsEnabledForPackage(
+                            localPackageName, appUid);
                     Notification localForegroundNoti = _foregroundNoti;
                     try {
                         if (localForegroundNoti.getSmallIcon() == null) {
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 6a716cb..066c263 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -1223,8 +1223,11 @@
             for (RouteInfo route : mConfig.routes) {
                 lp.addRoute(route);
                 InetAddress address = route.getDestination().getAddress();
-                allowIPv4 |= address instanceof Inet4Address;
-                allowIPv6 |= address instanceof Inet6Address;
+
+                if (route.getType() == RouteInfo.RTN_UNICAST) {
+                    allowIPv4 |= address instanceof Inet4Address;
+                    allowIPv6 |= address instanceof Inet6Address;
+                }
             }
         }
 
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 4822d6a..96391ac 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -52,7 +52,6 @@
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.SystemClock;
-import android.text.TextUtils;
 import android.util.Log;
 import android.view.KeyEvent;
 
@@ -643,22 +642,24 @@
     }
 
     private void pushQueueUpdate() {
-        ParceledListSlice<QueueItem> parcelableQueue;
+        ArrayList<QueueItem> toSend;
         synchronized (mLock) {
             if (mDestroyed) {
                 return;
             }
-            if (mQueue == null) {
-                parcelableQueue = null;
-            } else {
-                parcelableQueue = new ParceledListSlice<>(mQueue);
-                // Limit the size of initial Parcel to prevent binder buffer overflow
-                // as onQueueChanged is an async binder call.
-                parcelableQueue.setInlineCountLimit(1);
+            toSend = new ArrayList<>();
+            if (mQueue != null) {
+                toSend.ensureCapacity(mQueue.size());
+                toSend.addAll(mQueue);
             }
         }
         Collection<ISessionControllerCallbackHolder> deadCallbackHolders = null;
         for (ISessionControllerCallbackHolder holder : mControllerCallbackHolders) {
+            ParceledListSlice<QueueItem> parcelableQueue = new ParceledListSlice<>(toSend);
+            // Limit the size of initial Parcel to prevent binder buffer overflow
+            // as onQueueChanged is an async binder call.
+            parcelableQueue.setInlineCountLimit(1);
+
             try {
                 holder.mCallback.onQueueChanged(parcelableQueue);
             } catch (DeadObjectException e) {
diff --git a/services/core/java/com/android/server/media/OWNERS b/services/core/java/com/android/server/media/OWNERS
index 2e2d812..8097f4e 100644
--- a/services/core/java/com/android/server/media/OWNERS
+++ b/services/core/java/com/android/server/media/OWNERS
@@ -1,8 +1,6 @@
+# Bug component: 137631
 elaurent@google.com
-hdmoon@google.com
-insun@google.com
-jaewan@google.com
-jinpark@google.com
-klhyun@google.com
 lajos@google.com
-sungsoo@google.com
+
+# go/android-fwk-media-solutions for info on areas of ownership.
+include platform/frameworks/av:/media/janitors/media_solutions_OWNERS
diff --git a/services/core/java/com/android/server/notification/NotificationManagerInternal.java b/services/core/java/com/android/server/notification/NotificationManagerInternal.java
index 5455738..c548e7e 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerInternal.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerInternal.java
@@ -39,4 +39,7 @@
 
     /** Get the number of notification channels for a given package */
     int getNumNotificationChannelsForPackage(String pkg, int uid, boolean includeDeleted);
+
+    /** Does the specified package/uid have permission to post notifications? */
+    boolean areNotificationsEnabledForPackage(String pkg, int uid);
 }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index a0eeb65..399ae53 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -6205,6 +6205,11 @@
             return NotificationManagerService.this
                     .getNumNotificationChannelsForPackage(pkg, uid, includeDeleted);
         }
+
+        @Override
+        public boolean areNotificationsEnabledForPackage(String pkg, int uid) {
+            return areNotificationsEnabledForPackageInt(pkg, uid);
+        }
     };
 
     int getNumNotificationChannelsForPackage(String pkg, int uid, boolean includeDeleted) {
diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
index 05a51cc..31df0a5 100644
--- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java
+++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
@@ -896,10 +896,10 @@
         synchronized (mLock) {
             if (!mFinishedPostBootUpdate) {
                 mFinishedPostBootUpdate = true;
-                JobScheduler js = mInjector.getJobScheduler();
-                js.cancel(JOB_POST_BOOT_UPDATE);
             }
         }
+        // Safe to do this outside lock.
+        mInjector.getJobScheduler().cancel(JOB_POST_BOOT_UPDATE);
     }
 
     private void notifyPinService(ArraySet<String> updatedPackages) {
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 4d6b3c2..416b3a4 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -70,6 +70,7 @@
 import android.util.ArraySet;
 import android.util.AtomicFile;
 import android.util.ExceptionUtils;
+import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
@@ -121,7 +122,7 @@
 public class PackageInstallerService extends IPackageInstaller.Stub implements
         PackageSessionProvider {
     private static final String TAG = "PackageInstaller";
-    private static final boolean LOGD = false;
+    private static final boolean LOGD = Log.isLoggable(TAG, Log.DEBUG);
 
     private static final boolean DEBUG = Build.IS_DEBUGGABLE;
 
@@ -510,6 +511,7 @@
                 valid = true;
             }
             if (!valid) {
+                Slog.w(TAG, "Remove old session: " + session.sessionId);
                 // Remove expired sessions as well as child sessions if any
                 mSessions.remove(session.sessionId);
                 // Since this is early during boot we don't send
@@ -850,6 +852,9 @@
         mCallbacks.notifySessionCreated(session.sessionId, session.userId);
 
         mSettingsWriteRequest.schedule();
+        if (LOGD) {
+            Slog.d(TAG, "Created session id=" + sessionId + " staged=" + params.isStaged);
+        }
         return sessionId;
     }
 
diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java
index 10d9bbf..c9a8701 100644
--- a/services/core/java/com/android/server/policy/PermissionPolicyService.java
+++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java
@@ -1139,13 +1139,11 @@
         private boolean shouldForceShowNotificationPermissionRequest(@NonNull String pkgName,
                 @NonNull UserHandle user) {
             AndroidPackage pkg = mPackageManagerInternal.getPackage(pkgName);
-            // TODO(b/205888750): Remove platform key and permissionController lines after pregrants
-            // are in place
-            if (pkg == null || pkg.getPackageName() == null || pkg.isSignedWithPlatformKey()
-                    || pkg.getPackageName().contains("nexuslauncher")
+            if (pkg == null || pkg.getPackageName() == null
                     || Objects.equals(pkgName, mPackageManager.getPermissionControllerPackageName())
                     || pkg.getTargetSdkVersion() < Build.VERSION_CODES.M) {
-                // TODO(b/205888750) add warning logs when pregrants in place
+                Slog.w(LOG_TAG, "Cannot check for Notification prompt, no package for "
+                        + pkgName + " or pkg is Permission Controller");
                 return false;
             }
 
diff --git a/services/core/java/com/android/server/powerstats/PowerStatsLogger.java b/services/core/java/com/android/server/powerstats/PowerStatsLogger.java
index ef0079e..ca67597 100644
--- a/services/core/java/com/android/server/powerstats/PowerStatsLogger.java
+++ b/services/core/java/com/android/server/powerstats/PowerStatsLogger.java
@@ -35,7 +35,6 @@
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.VisibleForTesting;
-
 import com.android.server.powerstats.PowerStatsHALWrapper.IPowerStatsHALWrapper;
 import com.android.server.powerstats.ProtoStreamUtils.ChannelUtils;
 import com.android.server.powerstats.ProtoStreamUtils.EnergyConsumerResultUtils;
@@ -313,12 +312,12 @@
         return mStartWallTime;
     }
 
-    public PowerStatsLogger(Context context, File dataStoragePath,
+    public PowerStatsLogger(Context context, Looper looper, File dataStoragePath,
             String meterFilename, String meterCacheFilename,
             String modelFilename, String modelCacheFilename,
             String residencyFilename, String residencyCacheFilename,
             IPowerStatsHALWrapper powerStatsHALWrapper) {
-        super(Looper.getMainLooper());
+        super(looper);
         mStartWallTime = currentTimeMillis() - SystemClock.elapsedRealtime();
         if (DEBUG) Slog.d(TAG, "mStartWallTime: " + mStartWallTime);
         mPowerStatsHALWrapper = powerStatsHALWrapper;
diff --git a/services/core/java/com/android/server/powerstats/PowerStatsService.java b/services/core/java/com/android/server/powerstats/PowerStatsService.java
index bb52c1d..9953ca8 100644
--- a/services/core/java/com/android/server/powerstats/PowerStatsService.java
+++ b/services/core/java/com/android/server/powerstats/PowerStatsService.java
@@ -28,6 +28,7 @@
 import android.os.Environment;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.os.Looper;
 import android.os.UserHandle;
 import android.power.PowerStatsInternal;
 import android.util.Slog;
@@ -79,6 +80,9 @@
     private StatsPullAtomCallbackImpl mPullAtomCallback;
     @Nullable
     private PowerStatsInternal mPowerStatsInternal;
+    @Nullable
+    @GuardedBy("this")
+    private Looper mLooper;
 
     @VisibleForTesting
     static class Injector {
@@ -127,12 +131,12 @@
             }
         }
 
-        PowerStatsLogger createPowerStatsLogger(Context context, File dataStoragePath,
-                String meterFilename, String meterCacheFilename,
+        PowerStatsLogger createPowerStatsLogger(Context context, Looper looper,
+                File dataStoragePath, String meterFilename, String meterCacheFilename,
                 String modelFilename, String modelCacheFilename,
                 String residencyFilename, String residencyCacheFilename,
                 IPowerStatsHALWrapper powerStatsHALWrapper) {
-            return new PowerStatsLogger(context, dataStoragePath,
+            return new PowerStatsLogger(context, looper, dataStoragePath,
                 meterFilename, meterCacheFilename,
                 modelFilename, modelCacheFilename,
                 residencyFilename, residencyCacheFilename,
@@ -229,11 +233,11 @@
             mDataStoragePath = mInjector.createDataStoragePath();
 
             // Only start logger and triggers if initialization is successful.
-            mPowerStatsLogger = mInjector.createPowerStatsLogger(mContext, mDataStoragePath,
-                mInjector.createMeterFilename(), mInjector.createMeterCacheFilename(),
-                mInjector.createModelFilename(), mInjector.createModelCacheFilename(),
-                mInjector.createResidencyFilename(), mInjector.createResidencyCacheFilename(),
-                getPowerStatsHal());
+            mPowerStatsLogger = mInjector.createPowerStatsLogger(mContext, getLooper(),
+                    mDataStoragePath, mInjector.createMeterFilename(),
+                    mInjector.createMeterCacheFilename(), mInjector.createModelFilename(),
+                    mInjector.createModelCacheFilename(), mInjector.createResidencyFilename(),
+                    mInjector.createResidencyCacheFilename(), getPowerStatsHal());
             mBatteryTrigger = mInjector.createBatteryTrigger(mContext, mPowerStatsLogger);
             mTimerTrigger = mInjector.createTimerTrigger(mContext, mPowerStatsLogger);
         } else {
@@ -245,6 +249,17 @@
         return mInjector.getPowerStatsHALWrapperImpl();
     }
 
+    private Looper getLooper() {
+        synchronized (this) {
+            if (mLooper == null) {
+                HandlerThread thread = new HandlerThread(TAG);
+                thread.start();
+                return thread.getLooper();
+            }
+            return mLooper;
+        }
+    }
+
     public PowerStatsService(Context context) {
         this(context, new Injector());
     }
@@ -260,9 +275,7 @@
         private final Handler mHandler;
 
         LocalService() {
-            HandlerThread thread = new HandlerThread(TAG);
-            thread.start();
-            mHandler = new Handler(thread.getLooper());
+            mHandler = new Handler(getLooper());
         }
 
 
diff --git a/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java b/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java
index a2bf2fe..a4732c1 100644
--- a/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java
+++ b/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java
@@ -906,6 +906,94 @@
         }
 
         @Override
+        public void notifyVideoAvailable(IBinder sessionToken, int userId) {
+            final int callingUid = Binder.getCallingUid();
+            final int callingPid = Binder.getCallingPid();
+            final int resolvedUserId = resolveCallingUserId(callingPid, callingUid, userId,
+                    "notifyVideoAvailable");
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
+                                resolvedUserId);
+                        getSessionLocked(sessionState).notifyVideoAvailable();
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slogf.e(TAG, "error in notifyVideoAvailable", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public void notifyVideoUnavailable(IBinder sessionToken, int reason, int userId) {
+            final int callingUid = Binder.getCallingUid();
+            final int callingPid = Binder.getCallingPid();
+            final int resolvedUserId = resolveCallingUserId(callingPid, callingUid, userId,
+                    "notifyVideoUnavailable");
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
+                                resolvedUserId);
+                        getSessionLocked(sessionState).notifyVideoUnavailable(reason);
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slogf.e(TAG, "error in notifyVideoUnavailable", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public void notifyContentAllowed(IBinder sessionToken, int userId) {
+            final int callingUid = Binder.getCallingUid();
+            final int callingPid = Binder.getCallingPid();
+            final int resolvedUserId = resolveCallingUserId(callingPid, callingUid, userId,
+                    "notifyContentAllowed");
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
+                                resolvedUserId);
+                        getSessionLocked(sessionState).notifyContentAllowed();
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slogf.e(TAG, "error in notifyContentAllowed", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public void notifyContentBlocked(IBinder sessionToken, String rating, int userId) {
+            final int callingUid = Binder.getCallingUid();
+            final int callingPid = Binder.getCallingPid();
+            final int resolvedUserId = resolveCallingUserId(callingPid, callingUid, userId,
+                    "notifyContentBlocked");
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
+                                resolvedUserId);
+                        getSessionLocked(sessionState).notifyContentBlocked(rating);
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slogf.e(TAG, "error in notifyContentBlocked", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
         public void startIApp(IBinder sessionToken, int userId) {
             if (DEBUG) {
                 Slogf.d(TAG, "BinderService#start(userId=%d)", userId);
diff --git a/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java b/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java
index bd8d13b..6db25b7 100644
--- a/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java
+++ b/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java
@@ -20,6 +20,8 @@
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkCapabilities.TRANSPORT_TEST;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_FORBIDDEN;
+import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_REQUIRED;
 import static android.net.vcn.VcnUnderlyingNetworkTemplate.NETWORK_QUALITY_ANY;
 import static android.net.vcn.VcnUnderlyingNetworkTemplate.NETWORK_QUALITY_OK;
 
@@ -44,7 +46,7 @@
 import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
 import com.android.server.vcn.VcnContext;
 
-import java.util.LinkedHashSet;
+import java.util.List;
 import java.util.Set;
 
 /** @hide */
@@ -76,7 +78,7 @@
     public static int calculatePriorityClass(
             VcnContext vcnContext,
             UnderlyingNetworkRecord networkRecord,
-            LinkedHashSet<VcnUnderlyingNetworkTemplate> underlyingNetworkPriorities,
+            List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
             ParcelUuid subscriptionGroup,
             TelephonySubscriptionSnapshot snapshot,
             UnderlyingNetworkRecord currentlySelected,
@@ -94,7 +96,7 @@
         }
 
         int priorityIndex = 0;
-        for (VcnUnderlyingNetworkTemplate nwPriority : underlyingNetworkPriorities) {
+        for (VcnUnderlyingNetworkTemplate nwPriority : underlyingNetworkTemplates) {
             if (checkMatchesPriorityRule(
                     vcnContext,
                     nwPriority,
@@ -122,7 +124,11 @@
         // TODO: Check Network Quality reported by metric monitors/probers.
 
         final NetworkCapabilities caps = networkRecord.networkCapabilities;
-        if (!networkPriority.allowMetered() && !caps.hasCapability(NET_CAPABILITY_NOT_METERED)) {
+
+        final int meteredMatch = networkPriority.getMetered();
+        final boolean isMetered = !caps.hasCapability(NET_CAPABILITY_NOT_METERED);
+        if (meteredMatch == MATCH_REQUIRED && !isMetered
+                || meteredMatch == MATCH_FORBIDDEN && isMetered) {
             return false;
         }
 
@@ -171,7 +177,8 @@
             return false;
         }
 
-        if (networkPriority.getSsid() != null && networkPriority.getSsid() != caps.getSsid()) {
+        if (!networkPriority.getSsids().isEmpty()
+                && !networkPriority.getSsids().contains(caps.getSsid())) {
             return false;
         }
 
@@ -226,26 +233,31 @@
                         .getSystemService(TelephonyManager.class)
                         .createForSubscriptionId(subId);
 
-        if (!networkPriority.getAllowedOperatorPlmnIds().isEmpty()) {
+        if (!networkPriority.getOperatorPlmnIds().isEmpty()) {
             final String plmnId = subIdSpecificTelephonyMgr.getNetworkOperator();
-            if (!networkPriority.getAllowedOperatorPlmnIds().contains(plmnId)) {
+            if (!networkPriority.getOperatorPlmnIds().contains(plmnId)) {
                 return false;
             }
         }
 
-        if (!networkPriority.getAllowedSpecificCarrierIds().isEmpty()) {
+        if (!networkPriority.getSimSpecificCarrierIds().isEmpty()) {
             final int carrierId = subIdSpecificTelephonyMgr.getSimSpecificCarrierId();
-            if (!networkPriority.getAllowedSpecificCarrierIds().contains(carrierId)) {
+            if (!networkPriority.getSimSpecificCarrierIds().contains(carrierId)) {
                 return false;
             }
         }
 
-        if (!networkPriority.allowRoaming() && !caps.hasCapability(NET_CAPABILITY_NOT_ROAMING)) {
+        final int roamingMatch = networkPriority.getRoaming();
+        final boolean isRoaming = !caps.hasCapability(NET_CAPABILITY_NOT_ROAMING);
+        if (roamingMatch == MATCH_REQUIRED && !isRoaming
+                || roamingMatch == MATCH_FORBIDDEN && isRoaming) {
             return false;
         }
 
-        if (networkPriority.requireOpportunistic()) {
-            if (!isOpportunistic(snapshot, caps.getSubscriptionIds())) {
+        final int opportunisticMatch = networkPriority.getOpportunistic();
+        final boolean isOpportunistic = isOpportunistic(snapshot, caps.getSubscriptionIds());
+        if (opportunisticMatch == MATCH_REQUIRED) {
+            if (!isOpportunistic) {
                 return false;
             }
 
@@ -265,6 +277,8 @@
                             .contains(SubscriptionManager.getActiveDataSubscriptionId())) {
                 return false;
             }
+        } else if (opportunisticMatch == MATCH_FORBIDDEN && !isOpportunistic) {
+            return false;
         }
 
         return true;
diff --git a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java
index df2f0d5..c0488b1 100644
--- a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java
+++ b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java
@@ -32,7 +32,7 @@
 import com.android.server.vcn.VcnContext;
 
 import java.util.Comparator;
-import java.util.LinkedHashSet;
+import java.util.List;
 import java.util.Objects;
 
 /**
@@ -77,7 +77,7 @@
 
     static Comparator<UnderlyingNetworkRecord> getComparator(
             VcnContext vcnContext,
-            LinkedHashSet<VcnUnderlyingNetworkTemplate> underlyingNetworkPriorities,
+            List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
             ParcelUuid subscriptionGroup,
             TelephonySubscriptionSnapshot snapshot,
             UnderlyingNetworkRecord currentlySelected,
@@ -87,7 +87,7 @@
                     NetworkPriorityClassifier.calculatePriorityClass(
                             vcnContext,
                             left,
-                            underlyingNetworkPriorities,
+                            underlyingNetworkTemplates,
                             subscriptionGroup,
                             snapshot,
                             currentlySelected,
@@ -96,7 +96,7 @@
                     NetworkPriorityClassifier.calculatePriorityClass(
                             vcnContext,
                             right,
-                            underlyingNetworkPriorities,
+                            underlyingNetworkTemplates,
                             subscriptionGroup,
                             snapshot,
                             currentlySelected,
@@ -133,7 +133,7 @@
     void dump(
             VcnContext vcnContext,
             IndentingPrintWriter pw,
-            LinkedHashSet<VcnUnderlyingNetworkTemplate> underlyingNetworkPriorities,
+            List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
             ParcelUuid subscriptionGroup,
             TelephonySubscriptionSnapshot snapshot,
             UnderlyingNetworkRecord currentlySelected,
@@ -145,7 +145,7 @@
                 NetworkPriorityClassifier.calculatePriorityClass(
                         vcnContext,
                         this,
-                        underlyingNetworkPriorities,
+                        underlyingNetworkTemplates,
                         subscriptionGroup,
                         snapshot,
                         currentlySelected,
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 1bafd49..ddd624d 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -437,9 +437,9 @@
     private static final long START_AS_CALLER_TOKEN_EXPIRED_TIMEOUT =
             START_AS_CALLER_TOKEN_TIMEOUT_IMPL + 20 * MINUTE_IN_MILLIS;
 
-    // Activity tokens of system activities that are delegating their call to
-    // #startActivityByCaller, keyed by the permissionToken granted to the delegate.
-    final HashMap<IBinder, IBinder> mStartActivitySources = new HashMap<>();
+    // The component name of the delegated activities that are allowed to call
+    // #startActivityAsCaller with the one-time used permission token.
+    final HashMap<IBinder, ComponentName> mStartActivitySources = new HashMap<>();
 
     // Permission tokens that have expired, but we remember for error reporting.
     final ArrayList<IBinder> mExpiredStartAsCallerTokens = new ArrayList<>();
@@ -1513,7 +1513,7 @@
     }
 
     @Override
-    public IBinder requestStartActivityPermissionToken(IBinder delegatorToken) {
+    public IBinder requestStartActivityPermissionToken(ComponentName componentName) {
         int callingUid = Binder.getCallingUid();
         if (UserHandle.getAppId(callingUid) != SYSTEM_UID) {
             throw new SecurityException("Only the system process can request a permission token, "
@@ -1521,7 +1521,7 @@
         }
         IBinder permissionToken = new Binder();
         synchronized (mGlobalLock) {
-            mStartActivitySources.put(permissionToken, delegatorToken);
+            mStartActivitySources.put(permissionToken, componentName);
         }
 
         Message expireMsg = PooledLambda.obtainMessage(
@@ -1546,7 +1546,7 @@
         // 1)  The caller is an activity that is part of the core framework, and then only when it
         //     is running as the system.
         // 2)  The caller provides a valid permissionToken.  Permission tokens are one-time use and
-        //     can only be requested by a system activity, which may then delegate this call to
+        //     can only be requested from system uid, which may then delegate this call to
         //     another app.
         final ActivityRecord sourceRecord;
         final int targetUid;
@@ -1557,18 +1557,26 @@
             if (resultTo == null) {
                 throw new SecurityException("Must be called from an activity");
             }
-            final IBinder sourceToken;
+
+            sourceRecord = ActivityRecord.isInAnyTask(resultTo);
+            if (sourceRecord == null) {
+                throw new SecurityException("Called with bad activity token: " + resultTo);
+            }
+            if (sourceRecord.app == null) {
+                throw new SecurityException("Called without a process attached to activity");
+            }
+
+            final ComponentName componentName;
             if (permissionToken != null) {
                 // To even attempt to use a permissionToken, an app must also have this signature
                 // permission.
                 mAmInternal.enforceCallingPermission(
                         android.Manifest.permission.START_ACTIVITY_AS_CALLER,
                         "startActivityAsCaller");
-                // If called with a permissionToken, we want the sourceRecord from the delegator
-                // activity that requested this token.
-                sourceToken = mStartActivitySources.remove(permissionToken);
-                if (sourceToken == null) {
-                    // Invalid permissionToken, check if it recently expired.
+                // If called with a permissionToken, the caller must be the same component that
+                // was allowed to use the permissionToken.
+                componentName = mStartActivitySources.remove(permissionToken);
+                if (!sourceRecord.mActivityComponent.equals(componentName)) {
                     if (mExpiredStartAsCallerTokens.contains(permissionToken)) {
                         throw new SecurityException("Called with expired permission token: "
                                 + permissionToken);
@@ -1578,33 +1586,21 @@
                     }
                 }
             } else {
-                // This method was called directly by the source.
-                sourceToken = resultTo;
-            }
-
-            sourceRecord = ActivityRecord.isInAnyTask(sourceToken);
-            if (sourceRecord == null) {
-                throw new SecurityException("Called with bad activity token: " + sourceToken);
-            }
-            if (sourceRecord.app == null) {
-                throw new SecurityException("Called without a process attached to activity");
-            }
-
-            // Whether called directly or from a delegate, the source activity must be from the
-            // android package.
-            if (!sourceRecord.info.packageName.equals("android")) {
-                throw new SecurityException("Must be called from an activity that is "
-                        + "declared in the android package");
-            }
-
-            if (UserHandle.getAppId(sourceRecord.app.mUid) != SYSTEM_UID) {
-                // This is still okay, as long as this activity is running under the
-                // uid of the original calling activity.
-                if (sourceRecord.app.mUid != sourceRecord.launchedFromUid) {
-                    throw new SecurityException(
-                            "Calling activity in uid " + sourceRecord.app.mUid
-                                    + " must be system uid or original calling uid "
-                                    + sourceRecord.launchedFromUid);
+                // Whether called directly or from a delegate, the source activity must be from the
+                // android package.
+                if (!sourceRecord.info.packageName.equals("android")) {
+                    throw new SecurityException("Must be called from an activity that is "
+                            + "declared in the android package");
+                }
+                if (UserHandle.getAppId(sourceRecord.app.mUid) != SYSTEM_UID) {
+                    // This is still okay, as long as this activity is running under the
+                    // uid of the original calling activity.
+                    if (sourceRecord.app.mUid != sourceRecord.launchedFromUid) {
+                        throw new SecurityException(
+                                "Calling activity in uid " + sourceRecord.app.mUid
+                                        + " must be system uid or original calling uid "
+                                        + sourceRecord.launchedFromUid);
+                    }
                 }
             }
             if (ignoreTargetSecurity) {
diff --git a/services/core/java/com/android/server/wm/DisplayAreaPolicy.java b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
index 47622bc..9661e8d 100644
--- a/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
@@ -24,13 +24,11 @@
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
 import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
 import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
-import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
 import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER;
 import static android.window.DisplayAreaOrganizer.FEATURE_FULLSCREEN_MAGNIFICATION;
 import static android.window.DisplayAreaOrganizer.FEATURE_HIDE_DISPLAY_CUTOUT;
 import static android.window.DisplayAreaOrganizer.FEATURE_IME_PLACEHOLDER;
 import static android.window.DisplayAreaOrganizer.FEATURE_ONE_HANDED;
-import static android.window.DisplayAreaOrganizer.FEATURE_ONE_HANDED_BACKGROUND_PANEL;
 import static android.window.DisplayAreaOrganizer.FEATURE_WINDOWED_MAGNIFICATION;
 
 import static com.android.server.wm.DisplayAreaPolicyBuilder.Feature;
@@ -134,11 +132,6 @@
                         .except(TYPE_NAVIGATION_BAR, TYPE_NAVIGATION_BAR_PANEL, TYPE_STATUS_BAR,
                                 TYPE_NOTIFICATION_SHADE)
                         .build())
-                        .addFeature(new Feature.Builder(wmService.mPolicy,
-                                "OneHandedBackgroundPanel",
-                                FEATURE_ONE_HANDED_BACKGROUND_PANEL)
-                                .upTo(TYPE_WALLPAPER)
-                                .build())
                         .addFeature(new Feature.Builder(wmService.mPolicy, "OneHanded",
                                 FEATURE_ONE_HANDED)
                                 .all()
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index fe5c358..ac90ceb 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -310,6 +310,8 @@
             "com.android.clockwork.connectivity.WearConnectivityService";
     private static final String WEAR_POWER_SERVICE_CLASS =
             "com.android.clockwork.power.WearPowerService";
+    private static final String HEALTH_SERVICE_CLASS =
+            "com.google.android.clockwork.healthservices.HealthService";
     private static final String WEAR_SIDEKICK_SERVICE_CLASS =
             "com.google.android.clockwork.sidekick.SidekickService";
     private static final String WEAR_DISPLAYOFFLOAD_SERVICE_CLASS =
@@ -2499,6 +2501,10 @@
             mSystemServiceManager.startService(WEAR_POWER_SERVICE_CLASS);
             t.traceEnd();
 
+            t.traceBegin("StartHealthService");
+            mSystemServiceManager.startService(HEALTH_SERVICE_CLASS);
+            t.traceEnd();
+
             t.traceBegin("StartWearConnectivityService");
             mSystemServiceManager.startService(WEAR_CONNECTIVITY_SERVICE_CLASS);
             t.traceEnd();
diff --git a/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java
index 26b34fd..304fe5a 100644
--- a/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java
@@ -30,6 +30,7 @@
 import android.hardware.power.stats.State;
 import android.hardware.power.stats.StateResidency;
 import android.hardware.power.stats.StateResidencyResult;
+import android.os.Looper;
 
 import androidx.test.InstrumentationRegistry;
 
@@ -145,12 +146,12 @@
         }
 
         @Override
-        PowerStatsLogger createPowerStatsLogger(Context context, File dataStoragePath,
-                String meterFilename, String meterCacheFilename,
+        PowerStatsLogger createPowerStatsLogger(Context context, Looper looper,
+                File dataStoragePath, String meterFilename, String meterCacheFilename,
                 String modelFilename, String modelCacheFilename,
                 String residencyFilename, String residencyCacheFilename,
                 IPowerStatsHALWrapper powerStatsHALWrapper) {
-            mPowerStatsLogger = new PowerStatsLogger(context, dataStoragePath,
+            mPowerStatsLogger = new PowerStatsLogger(context, looper, dataStoragePath,
                 meterFilename, meterCacheFilename,
                 modelFilename, modelCacheFilename,
                 residencyFilename, residencyCacheFilename,
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 11777ef..d831903 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -182,7 +182,6 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.AtomicFile;
-import android.util.Slog;
 import android.util.TypedXmlPullParser;
 import android.util.TypedXmlSerializer;
 import android.util.Xml;
@@ -7174,6 +7173,14 @@
     }
 
     @Test
+    public void testAreNotificationsEnabledForPackage_viaInternalService() throws Exception {
+        assertEquals(mInternalService.areNotificationsEnabledForPackage(
+                mContext.getPackageName(), mUid),
+                mBinderService.areNotificationsEnabledForPackage(mContext.getPackageName(), mUid));
+        verify(mPermissionHelper, never()).hasPermission(anyInt());
+    }
+
+    @Test
     public void testAreBubblesAllowedForPackage_crossUser() throws Exception {
         try {
             mBinderService.getBubblePreferenceForPackage(mContext.getPackageName(),
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java
index 0c8fe35..1362628 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java
@@ -536,6 +536,12 @@
     }
 
     @Test
+    public void testAreNotificationsEnabledForPackage_viaInternalService() {
+        mInternalService.areNotificationsEnabledForPackage(mContext.getPackageName(), mUid);
+        verify(mPermissionHelper).hasPermission(mUid);
+    }
+
+    @Test
     public void testGetPackageImportance() throws Exception {
         when(mPermissionHelper.hasPermission(mUid)).thenReturn(true);
         assertThat(mBinderService.getPackageImportance(mContext.getPackageName()))
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java
index 525888d..cb9eb52 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java
@@ -30,7 +30,6 @@
 import static android.window.DisplayAreaOrganizer.FEATURE_FULLSCREEN_MAGNIFICATION;
 import static android.window.DisplayAreaOrganizer.FEATURE_IME_PLACEHOLDER;
 import static android.window.DisplayAreaOrganizer.FEATURE_ONE_HANDED;
-import static android.window.DisplayAreaOrganizer.FEATURE_ONE_HANDED_BACKGROUND_PANEL;
 import static android.window.DisplayAreaOrganizer.FEATURE_ROOT;
 import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_FIRST;
 import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_LAST;
@@ -197,25 +196,6 @@
     }
 
     @Test
-    public void testBuilder_defaultPolicy_hasOneHandedBackgroundFeature() {
-        final DisplayAreaPolicy.Provider defaultProvider = DisplayAreaPolicy.Provider.fromResources(
-                resourcesWithProvider(""));
-        final DisplayAreaPolicyBuilder.Result defaultPolicy =
-                (DisplayAreaPolicyBuilder.Result) defaultProvider.instantiate(mWms, mDisplayContent,
-                        mRoot, mImeContainer);
-        if (mDisplayContent.isDefaultDisplay) {
-            final List<Feature> features = defaultPolicy.getFeatures();
-            boolean hasOneHandedBackgroundFeature = false;
-            for (Feature feature : features) {
-                hasOneHandedBackgroundFeature |=
-                        feature.getId() == FEATURE_ONE_HANDED_BACKGROUND_PANEL;
-            }
-
-            assertThat(hasOneHandedBackgroundFeature).isTrue();
-        }
-    }
-
-    @Test
     public void testBuilder_defaultPolicy_hasWindowedMagnificationFeature() {
         final DisplayAreaPolicy.Provider defaultProvider = DisplayAreaPolicy.Provider.fromResources(
                 resourcesWithProvider(""));
diff --git a/tests/AppLaunchWear/Android.bp b/tests/AppLaunchWear/Android.bp
deleted file mode 100644
index e2fc473..0000000
--- a/tests/AppLaunchWear/Android.bp
+++ /dev/null
@@ -1,22 +0,0 @@
-package {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "frameworks_base_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    default_applicable_licenses: ["frameworks_base_license"],
-}
-
-android_test {
-    name: "AppLaunchWear",
-    // Only compile source java files in this apk.
-    srcs: ["src/**/*.java"],
-    platform_apis: true,
-    certificate: "platform",
-    libs: [
-        "android.test.base",
-        "android.test.runner",
-    ],
-    static_libs: ["androidx.test.rules"],
-    test_suites: ["device-tests"],
-}
diff --git a/tests/AppLaunchWear/AndroidManifest.xml b/tests/AppLaunchWear/AndroidManifest.xml
deleted file mode 100644
index 7dfd7ba..0000000
--- a/tests/AppLaunchWear/AndroidManifest.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.tests.applaunch"
-    android:sharedUserId="android.uid.system" >
-
-   <uses-permission android:name="android.permission.REAL_GET_TASKS" />
-   <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-
-   <uses-sdk
-        android:minSdkVersion="22"
-        android:targetSdkVersion="24" />
-
-    <instrumentation android:label="Measure app start up time"
-                     android:name="android.test.InstrumentationTestRunner"
-                     android:targetPackage="com.android.tests.applaunch" />
-
-    <application android:label="App Launch Test">
-        <uses-library android:name="android.test.runner" />
-    </application>
-</manifest>
diff --git a/tests/AppLaunchWear/src/com/android/tests/applaunch/AppLaunch.java b/tests/AppLaunchWear/src/com/android/tests/applaunch/AppLaunch.java
deleted file mode 100644
index 97701c6..0000000
--- a/tests/AppLaunchWear/src/com/android/tests/applaunch/AppLaunch.java
+++ /dev/null
@@ -1,864 +0,0 @@
-/*
- * Copyright (C) 2013 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.tests.applaunch;
-
-import android.accounts.Account;
-import android.accounts.AccountManager;
-import android.app.ActivityManager;
-import android.app.ActivityManager.ProcessErrorStateInfo;
-import android.app.IActivityManager;
-import android.app.UiAutomation;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.ResolveInfo;
-import android.os.Bundle;
-import android.os.ParcelFileDescriptor;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.test.InstrumentationTestCase;
-import android.test.InstrumentationTestRunner;
-import android.util.Log;
-
-import androidx.test.rule.logging.AtraceLogger;
-
-import java.io.BufferedReader;
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStreamWriter;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * This test is intended to measure the time it takes for the apps to start.
- * Names of the applications are passed in command line, and the
- * test starts each application, and reports the start up time in milliseconds.
- * The instrumentation expects the following key to be passed on the command line:
- * apps - A list of applications to start and their corresponding result keys
- * in the following format:
- * -e apps <app name>^<result key>|<app name>^<result key>
- */
-public class AppLaunch extends InstrumentationTestCase {
-
-    private static final int JOIN_TIMEOUT = 10000;
-    private static final String TAG = AppLaunch.class.getSimpleName();
-
-    // optional parameter: comma separated list of required account types before proceeding
-    // with the app launch
-    private static final String KEY_REQUIRED_ACCOUNTS = "required_accounts";
-    private static final String KEY_APPS = "apps";
-    private static final String KEY_TRIAL_LAUNCH = "trial_launch";
-    private static final String KEY_LAUNCH_ITERATIONS = "launch_iterations";
-    private static final String KEY_LAUNCH_ORDER = "launch_order";
-    private static final String KEY_DROP_CACHE = "drop_cache";
-    private static final String KEY_SIMPLEPERF_CMD = "simpleperf_cmd";
-    private static final String KEY_SIMPLEPERF_APP = "simpleperf_app";
-    private static final String KEY_TRACE_ITERATIONS = "trace_iterations";
-    private static final String KEY_LAUNCH_DIRECTORY = "launch_directory";
-    private static final String KEY_TRACE_DIRECTORY = "trace_directory";
-    private static final String KEY_TRACE_CATEGORY = "trace_categories";
-    private static final String KEY_TRACE_BUFFERSIZE = "trace_bufferSize";
-    private static final String KEY_TRACE_DUMPINTERVAL = "tracedump_interval";
-    private static final String KEY_COMPILER_FILTERS = "compiler_filters";
-
-    private static final String SIMPLEPERF_APP_CMD =
-            "simpleperf --log fatal stat --csv -e cpu-cycles,major-faults --app %s & %s";
-    private static final String WEARABLE_ACTION_GOOGLE =
-            "com.google.android.wearable.action.GOOGLE";
-    private static final int INITIAL_LAUNCH_IDLE_TIMEOUT = 5000; // 5s to allow app to idle
-    private static final int POST_LAUNCH_IDLE_TIMEOUT = 750; // 750ms idle for non initial launches
-    private static final int BETWEEN_LAUNCH_SLEEP_TIMEOUT = 5000; // 5s between launching apps
-    private static final String LAUNCH_SUB_DIRECTORY = "launch_logs";
-    private static final String LAUNCH_FILE = "applaunch.txt";
-    private static final String TRACE_SUB_DIRECTORY = "atrace_logs";
-    private static final String DEFAULT_TRACE_CATEGORIES =
-            "sched,freq,gfx,view,dalvik,webview,input,wm,disk,am,wm";
-    private static final String DEFAULT_TRACE_BUFFER_SIZE = "20000";
-    private static final String DEFAULT_TRACE_DUMP_INTERVAL = "10";
-    private static final String TRIAL_LAUNCH = "TRIAL_LAUNCH";
-    private static final String DELIMITER = ",";
-    private static final String DROP_CACHE_SCRIPT = "/data/local/tmp/dropCache.sh";
-    private static final String APP_LAUNCH_CMD = "am start -W -n";
-    private static final String SUCCESS_MESSAGE = "Status: ok";
-    private static final String WARNING_MESSAGE = "Warning: Activity not started";
-    private static final String COMPILE_SUCCESS = "Success";
-    private static final String THIS_TIME = "ThisTime:";
-    private static final String LAUNCH_ITERATION = "LAUNCH_ITERATION - %d";
-    private static final String TRACE_ITERATION = "TRACE_ITERATION-%d";
-    private static final String LAUNCH_ITERATION_PREFIX = "LAUNCH_ITERATION";
-    private static final String TRACE_ITERATION_PREFIX = "TRACE_ITERATION";
-    private static final String LAUNCH_ORDER_CYCLIC = "cyclic";
-    private static final String LAUNCH_ORDER_SEQUENTIAL = "sequential";
-    private static final String COMPILE_CMD = "cmd package compile -f -m %s %s";
-    private static final String SPEED_PROFILE_FILTER = "speed-profile";
-    private static final String VERIFY_FILTER = "verify";
-    private static final String LAUNCH_SCRIPT_NAME = "appLaunch";
-    private static final String WEARABLE_HOME_PACKAGE = "com.google.android.wearable.app";
-
-    private Map<String, Intent> mNameToIntent;
-    private List<LaunchOrder> mLaunchOrderList = new ArrayList<LaunchOrder>();
-    private Map<String, String> mNameToResultKey;
-    private Map<String, Map<String, List<AppLaunchResult>>> mNameToLaunchTime;
-    private IActivityManager mAm;
-    private String mSimplePerfCmd = null;
-    private String mLaunchOrder = null;
-    private boolean mDropCache = false;
-    private int mLaunchIterations = 10;
-    private int mTraceLaunchCount = 0;
-    private String mTraceDirectoryStr = null;
-    private Bundle mResult = new Bundle();
-    private Set<String> mRequiredAccounts;
-    private boolean mTrialLaunch = false;
-    private BufferedWriter mBufferedWriter = null;
-    private boolean mSimplePerfAppOnly = false;
-    private String[] mCompilerFilters = null;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        getInstrumentation().getUiAutomation().setRotation(UiAutomation.ROTATION_FREEZE_0);
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        getInstrumentation().getUiAutomation().setRotation(UiAutomation.ROTATION_UNFREEZE);
-        super.tearDown();
-    }
-
-    private void addLaunchResult(LaunchOrder launch, AppLaunchResult result) {
-        mNameToLaunchTime.get(launch.getApp()).get(launch.getCompilerFilter()).add(result);
-    }
-
-    private boolean hasFailureOnFirstLaunch(LaunchOrder launch) {
-        List<AppLaunchResult> results =
-            mNameToLaunchTime.get(launch.getApp()).get(launch.getCompilerFilter());
-        return (results.size() > 0) && (results.get(0).mLaunchTime < 0);
-    }
-
-    public void testMeasureStartUpTime() throws RemoteException, NameNotFoundException,
-            IOException, InterruptedException {
-        InstrumentationTestRunner instrumentation =
-                (InstrumentationTestRunner)getInstrumentation();
-        Bundle args = instrumentation.getArguments();
-        mAm = ActivityManager.getService();
-        String launchDirectory = args.getString(KEY_LAUNCH_DIRECTORY);
-
-        createMappings();
-        parseArgs(args);
-        checkAccountSignIn();
-
-        // Root directory for applaunch file to log the app launch output
-        // Will be useful in case of simpleperf command is used
-        File launchRootDir = null;
-        if (null != launchDirectory && !launchDirectory.isEmpty()) {
-            launchRootDir = new File(launchDirectory);
-            if (!launchRootDir.exists() && !launchRootDir.mkdirs()) {
-                throw new IOException("Unable to create the destination directory");
-            }
-        }
-
-        try {
-            File launchSubDir = new File(launchRootDir, LAUNCH_SUB_DIRECTORY);
-
-            if (!launchSubDir.exists() && !launchSubDir.mkdirs()) {
-                throw new IOException("Unable to create the lauch file sub directory");
-            }
-            File file = new File(launchSubDir, LAUNCH_FILE);
-            FileOutputStream outputStream = new FileOutputStream(file);
-            mBufferedWriter = new BufferedWriter(new OutputStreamWriter(
-                    outputStream));
-
-            // Root directory for trace file during the launches
-            File rootTrace = null;
-            File rootTraceSubDir = null;
-            int traceBufferSize = 0;
-            int traceDumpInterval = 0;
-            Set<String> traceCategoriesSet = null;
-            if (null != mTraceDirectoryStr && !mTraceDirectoryStr.isEmpty()) {
-                rootTrace = new File(mTraceDirectoryStr);
-                if (!rootTrace.exists() && !rootTrace.mkdirs()) {
-                    throw new IOException("Unable to create the trace directory");
-                }
-                rootTraceSubDir = new File(rootTrace, TRACE_SUB_DIRECTORY);
-                if (!rootTraceSubDir.exists() && !rootTraceSubDir.mkdirs()) {
-                    throw new IOException("Unable to create the trace sub directory");
-                }
-                assertNotNull("Trace iteration parameter is mandatory",
-                        args.getString(KEY_TRACE_ITERATIONS));
-                mTraceLaunchCount = Integer.parseInt(args.getString(KEY_TRACE_ITERATIONS));
-                String traceCategoriesStr = args
-                        .getString(KEY_TRACE_CATEGORY, DEFAULT_TRACE_CATEGORIES);
-                traceBufferSize = Integer.parseInt(args.getString(KEY_TRACE_BUFFERSIZE,
-                        DEFAULT_TRACE_BUFFER_SIZE));
-                traceDumpInterval = Integer.parseInt(args.getString(KEY_TRACE_DUMPINTERVAL,
-                        DEFAULT_TRACE_DUMP_INTERVAL));
-                traceCategoriesSet = new HashSet<String>();
-                if (!traceCategoriesStr.isEmpty()) {
-                    String[] traceCategoriesSplit = traceCategoriesStr.split(DELIMITER);
-                    for (int i = 0; i < traceCategoriesSplit.length; i++) {
-                        traceCategoriesSet.add(traceCategoriesSplit[i]);
-                    }
-                }
-            }
-
-            // Get the app launch order based on launch order, trial launch,
-            // launch iterations and trace iterations
-            setLaunchOrder();
-
-            for (LaunchOrder launch : mLaunchOrderList) {
-                if (mNameToIntent.get(launch.getApp()) == null) {
-                    continue;
-                }
-                dropCache();
-                String appPkgName = mNameToIntent.get(launch.getApp())
-                        .getComponent().getPackageName();
-                Log.v(TAG, String.format("\nApp name: %s", launch.getApp()));
-                Log.v(TAG, String.format("Adding app package name: %s", appPkgName));
-                // App launch times for trial launch will not be used for final
-                // launch time calculations.
-                if (launch.getLaunchReason().equals(TRIAL_LAUNCH)) {
-                    // In the "applaunch.txt" file, trail launches is referenced using
-                    // "TRIAL_LAUNCH"
-                    Log.v(TAG, "Trial Launch");
-                    if (SPEED_PROFILE_FILTER.equals(launch.getCompilerFilter())) {
-                        assertTrue(String.format("Not able to compile the app : %s", appPkgName),
-                              compileApp(VERIFY_FILTER, appPkgName));
-                    } else if (launch.getCompilerFilter() != null) {
-                        assertTrue(String.format("Not able to compile the app : %s", appPkgName),
-                              compileApp(launch.getCompilerFilter(), appPkgName));
-                    }
-                    // We only need to run a trial for the speed-profile filter, but we always
-                    // run one for "applaunch.txt" consistency.
-                    AppLaunchResult launchResult = null;
-                    if (appPkgName.contains(WEARABLE_HOME_PACKAGE)) {
-                        Log.v(TAG, "Home package detected. Not killing app");
-                        launchResult = startApp(launch.getApp(), false, launch.getLaunchReason());
-                    } else {
-                        Log.v(TAG, "Will kill app before launch");
-                        launchResult = startApp(launch.getApp(), true, launch.getLaunchReason());
-                    }
-                    if (launchResult.mLaunchTime < 0) {
-                        addLaunchResult(launch, new AppLaunchResult());
-                        // simply pass the app if launch isn't successful
-                        // error should have already been logged by startApp
-                        continue;
-                    }
-                    sleep(INITIAL_LAUNCH_IDLE_TIMEOUT);
-                    if (SPEED_PROFILE_FILTER.equals(launch.getCompilerFilter())) {
-                        // Send SIGUSR1 to force dumping a profile.
-                        String sendSignalCommand =
-                            String.format("killall -s SIGUSR1 %s", appPkgName);
-                        getInstrumentation().getUiAutomation().executeShellCommand(
-                            sendSignalCommand);
-                        assertTrue(String.format("Not able to compile the app : %s", appPkgName),
-                              compileApp(launch.getCompilerFilter(), appPkgName));
-                    }
-                }
-
-                // App launch times used for final calculation
-                else if (launch.getLaunchReason().contains(LAUNCH_ITERATION_PREFIX)) {
-                    Log.v(TAG, "Launch iteration prefix.");
-                    AppLaunchResult launchResults = null;
-                    if (hasFailureOnFirstLaunch(launch)) {
-                        // skip if the app has failures while launched first
-                        continue;
-                    }
-                    // In the "applaunch.txt" file app launches are referenced using
-                    // "LAUNCH_ITERATION - ITERATION NUM"
-                    if (appPkgName.contains(WEARABLE_HOME_PACKAGE)) {
-                        Log.v(TAG, "Home package detected. Not killing app");
-                        launchResults = startApp(launch.getApp(), false, launch.getLaunchReason());
-                    } else {
-                        Log.v(TAG, "Will kill app before launch");
-                        launchResults = startApp(launch.getApp(), true, launch.getLaunchReason());
-                    }
-                    if (launchResults.mLaunchTime < 0) {
-                        addLaunchResult(launch, new AppLaunchResult());
-                        // if it fails once, skip the rest of the launches
-                        continue;
-                    } else {
-                        addLaunchResult(launch, launchResults);
-                    }
-                    sleep(POST_LAUNCH_IDLE_TIMEOUT);
-                }
-
-                // App launch times for trace launch will not be used for final
-                // launch time calculations.
-                else if (launch.getLaunchReason().contains(TRACE_ITERATION_PREFIX)) {
-                    Log.v(TAG, "Trace iteration prefix");
-                    AtraceLogger atraceLogger = AtraceLogger
-                            .getAtraceLoggerInstance(getInstrumentation());
-                    // Start the trace
-                    try {
-                        atraceLogger.atraceStart(traceCategoriesSet, traceBufferSize,
-                                traceDumpInterval, rootTraceSubDir,
-                                String.format("%s-%s", launch.getApp(), launch.getLaunchReason()));
-                        if (appPkgName.contains(WEARABLE_HOME_PACKAGE)) {
-                            Log.v(TAG, "Home package detected. Not killing app");
-                            startApp(launch.getApp(), false, launch.getLaunchReason());
-                        } else {
-                            Log.v(TAG, "Will kill app before launch");
-                            startApp(launch.getApp(), true, launch.getLaunchReason());
-                        }
-                        sleep(POST_LAUNCH_IDLE_TIMEOUT);
-                    } finally {
-                        // Stop the trace
-                        atraceLogger.atraceStop();
-                    }
-                }
-                closeApp(launch.getApp(), true);
-                sleep(BETWEEN_LAUNCH_SLEEP_TIMEOUT);
-            }
-        } finally {
-            if (null != mBufferedWriter) {
-                mBufferedWriter.close();
-            }
-        }
-
-        for (String app : mNameToResultKey.keySet()) {
-            for (String compilerFilter : mCompilerFilters) {
-                StringBuilder launchTimes = new StringBuilder();
-                StringBuilder cpuCycles = new StringBuilder();
-                StringBuilder majorFaults = new StringBuilder();
-                for (AppLaunchResult result : mNameToLaunchTime.get(app).get(compilerFilter)) {
-                    launchTimes.append(result.mLaunchTime);
-                    launchTimes.append(",");
-                    if (mSimplePerfAppOnly) {
-                        cpuCycles.append(result.mCpuCycles);
-                        cpuCycles.append(",");
-                        majorFaults.append(result.mMajorFaults);
-                        majorFaults.append(",");
-                    }
-                }
-                String filterName = (compilerFilter == null) ? "" : ("-" + compilerFilter);
-                mResult.putString(mNameToResultKey.get(app) + filterName, launchTimes.toString());
-                if (mSimplePerfAppOnly) {
-                    mResult.putString(mNameToResultKey.get(app) + filterName + "-cpuCycles",
-                        cpuCycles.toString());
-                    mResult.putString(mNameToResultKey.get(app) + filterName + "-majorFaults",
-                        majorFaults.toString());
-                }
-            }
-        }
-        instrumentation.sendStatus(0, mResult);
-    }
-
-    /**
-     * Compile the app package using compilerFilter and return true or false
-     * based on status of the compilation command.
-     */
-    private boolean compileApp(String compilerFilter, String appPkgName) throws IOException {
-        try (ParcelFileDescriptor result = getInstrumentation().getUiAutomation().
-                executeShellCommand(String.format(COMPILE_CMD, compilerFilter, appPkgName));
-                BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(
-                        new FileInputStream(result.getFileDescriptor())))) {
-            String line;
-            while ((line = bufferedReader.readLine()) != null) {
-                if (line.contains(COMPILE_SUCCESS)) {
-                    return true;
-                }
-            }
-            return false;
-        }
-    }
-
-    /**
-     * If launch order is "cyclic" then apps will be launched one after the
-     * other for each iteration count.
-     * If launch order is "sequential" then each app will be launched for given number
-     * iterations at once before launching the other apps.
-     */
-    private void setLaunchOrder() {
-        if (LAUNCH_ORDER_CYCLIC.equalsIgnoreCase(mLaunchOrder)) {
-            for (String compilerFilter : mCompilerFilters) {
-                if (mTrialLaunch) {
-                    for (String app : mNameToResultKey.keySet()) {
-                        mLaunchOrderList.add(new LaunchOrder(app, compilerFilter, TRIAL_LAUNCH));
-                    }
-                }
-                for (int launchCount = 0; launchCount < mLaunchIterations; launchCount++) {
-                    for (String app : mNameToResultKey.keySet()) {
-                        mLaunchOrderList.add(new LaunchOrder(app, compilerFilter,
-                                  String.format(LAUNCH_ITERATION, launchCount)));
-                    }
-                }
-                if (mTraceDirectoryStr != null && !mTraceDirectoryStr.isEmpty()) {
-                    for (int traceCount = 0; traceCount < mTraceLaunchCount; traceCount++) {
-                        for (String app : mNameToResultKey.keySet()) {
-                            mLaunchOrderList.add(new LaunchOrder(app, compilerFilter,
-                                      String.format(TRACE_ITERATION, traceCount)));
-                        }
-                    }
-                }
-            }
-        } else if (LAUNCH_ORDER_SEQUENTIAL.equalsIgnoreCase(mLaunchOrder)) {
-            for (String compilerFilter : mCompilerFilters) {
-                for (String app : mNameToResultKey.keySet()) {
-                    if (mTrialLaunch) {
-                        mLaunchOrderList.add(new LaunchOrder(app, compilerFilter, TRIAL_LAUNCH));
-                    }
-                    for (int launchCount = 0; launchCount < mLaunchIterations; launchCount++) {
-                        mLaunchOrderList.add(new LaunchOrder(app, compilerFilter,
-                                String.format(LAUNCH_ITERATION, launchCount)));
-                    }
-                    if (mTraceDirectoryStr != null && !mTraceDirectoryStr.isEmpty()) {
-                        for (int traceCount = 0; traceCount < mTraceLaunchCount; traceCount++) {
-                            mLaunchOrderList.add(new LaunchOrder(app, compilerFilter,
-                                    String.format(TRACE_ITERATION, traceCount)));
-                        }
-                    }
-                }
-            }
-        } else {
-            assertTrue("Launch order is not valid parameter", false);
-        }
-    }
-
-    private void dropCache() {
-        if (mDropCache) {
-            assertNotNull("Issue in dropping the cache",
-                    getInstrumentation().getUiAutomation()
-                            .executeShellCommand(DROP_CACHE_SCRIPT));
-        }
-    }
-
-    private void parseArgs(Bundle args) {
-        mNameToResultKey = new LinkedHashMap<String, String>();
-        mNameToLaunchTime = new HashMap<>();
-        String launchIterations = args.getString(KEY_LAUNCH_ITERATIONS);
-        if (launchIterations != null) {
-            mLaunchIterations = Integer.parseInt(launchIterations);
-        }
-        String appList = args.getString(KEY_APPS);
-        if (appList == null)
-            return;
-
-        String appNames[] = appList.split("\\|");
-        for (String pair : appNames) {
-            String[] parts = pair.split("\\^");
-            if (parts.length != 2) {
-                Log.e(TAG, "The apps key is incorrectly formatted");
-                fail();
-            }
-
-            mNameToResultKey.put(parts[0], parts[1]);
-            mNameToLaunchTime.put(parts[0], null);
-        }
-        String requiredAccounts = args.getString(KEY_REQUIRED_ACCOUNTS);
-        if (requiredAccounts != null) {
-            mRequiredAccounts = new HashSet<String>();
-            for (String accountType : requiredAccounts.split(",")) {
-                mRequiredAccounts.add(accountType);
-            }
-        }
-
-        String compilerFilterList = args.getString(KEY_COMPILER_FILTERS);
-        if (compilerFilterList != null) {
-            // If a compiler filter is passed, we make a trial launch to force compilation
-            // of the apps.
-            mTrialLaunch = true;
-            mCompilerFilters = compilerFilterList.split("\\|");
-        } else {
-            // Just pass a null compiler filter to use the current state of the app.
-            mCompilerFilters = new String[1];
-        }
-
-        // Pre-populate the results map to avoid null checks.
-        for (String app : mNameToLaunchTime.keySet()) {
-            HashMap<String, List<AppLaunchResult>> map = new HashMap<>();
-            mNameToLaunchTime.put(app, map);
-            for (String compilerFilter : mCompilerFilters) {
-                map.put(compilerFilter, new ArrayList<>());
-            }
-        }
-
-        mTraceDirectoryStr = args.getString(KEY_TRACE_DIRECTORY);
-        mDropCache = Boolean.parseBoolean(args.getString(KEY_DROP_CACHE));
-        mSimplePerfCmd = args.getString(KEY_SIMPLEPERF_CMD);
-        mLaunchOrder = args.getString(KEY_LAUNCH_ORDER, LAUNCH_ORDER_CYCLIC);
-        mSimplePerfAppOnly = Boolean.parseBoolean(args.getString(KEY_SIMPLEPERF_APP));
-        mTrialLaunch = mTrialLaunch || Boolean.parseBoolean(args.getString(KEY_TRIAL_LAUNCH));
-
-        if (mSimplePerfCmd != null && mSimplePerfAppOnly) {
-            Log.w(TAG, String.format("Passing both %s and %s is not supported, ignoring %s",
-                KEY_SIMPLEPERF_CMD, KEY_SIMPLEPERF_APP));
-        }
-    }
-
-    private boolean hasLeanback(Context context) {
-        return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK);
-    }
-
-    private void createMappings() {
-        mNameToIntent = new LinkedHashMap<String, Intent>();
-
-        PackageManager pm = getInstrumentation().getContext()
-                .getPackageManager();
-        Intent intentToResolve = new Intent(Intent.ACTION_MAIN);
-        intentToResolve.addCategory(hasLeanback(getInstrumentation().getContext()) ?
-                Intent.CATEGORY_LEANBACK_LAUNCHER :
-                Intent.CATEGORY_LAUNCHER);
-        List<ResolveInfo> ris = pm.queryIntentActivities(intentToResolve, 0);
-        resolveLoop(ris, intentToResolve, pm);
-        // For Wear
-        intentToResolve = new Intent(WEARABLE_ACTION_GOOGLE);
-        ris = pm.queryIntentActivities(intentToResolve, 0);
-        resolveLoop(ris, intentToResolve, pm);
-    }
-
-    private void resolveLoop(List<ResolveInfo> ris, Intent intentToResolve, PackageManager pm) {
-        if (ris == null || ris.isEmpty()) {
-            Log.i(TAG, "Could not find any apps");
-        } else {
-            for (ResolveInfo ri : ris) {
-                Intent startIntent = new Intent(intentToResolve);
-                startIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
-                        | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
-                startIntent.setClassName(ri.activityInfo.packageName,
-                        ri.activityInfo.name);
-                String appName = ri.loadLabel(pm).toString();
-                if (appName != null) {
-                    // Support launching intent using package name or app name
-                    mNameToIntent.put(ri.activityInfo.packageName, startIntent);
-                    mNameToIntent.put(appName, startIntent);
-                }
-            }
-        }
-    }
-
-    private AppLaunchResult startApp(String appName, boolean forceStopBeforeLaunch,
-            String launchReason) throws NameNotFoundException, RemoteException {
-        Log.i(TAG, "Starting " + appName);
-
-        Intent startIntent = mNameToIntent.get(appName);
-        if (startIntent == null) {
-            Log.w(TAG, "App does not exist: " + appName);
-            mResult.putString(mNameToResultKey.get(appName), "App does not exist");
-            return new AppLaunchResult();
-        }
-        AppLaunchRunnable runnable = new AppLaunchRunnable(startIntent, forceStopBeforeLaunch,
-                launchReason);
-        Thread t = new Thread(runnable);
-        t.start();
-        try {
-            t.join(JOIN_TIMEOUT);
-        } catch (InterruptedException e) {
-            // ignore
-        }
-        return runnable.getResult();
-    }
-
-    private void checkAccountSignIn() {
-        // ensure that the device has the required account types before starting test
-        // e.g. device must have a valid Google account sign in to measure a meaningful launch time
-        // for Gmail
-        if (mRequiredAccounts == null || mRequiredAccounts.isEmpty()) {
-            return;
-        }
-        final AccountManager am =
-                (AccountManager) getInstrumentation().getTargetContext().getSystemService(
-                        Context.ACCOUNT_SERVICE);
-        Account[] accounts = am.getAccounts();
-        // use set here in case device has multiple accounts of the same type
-        Set<String> foundAccounts = new HashSet<String>();
-        for (Account account : accounts) {
-            if (mRequiredAccounts.contains(account.type)) {
-                foundAccounts.add(account.type);
-            }
-        }
-        // check if account type matches, if not, fail test with message on what account types
-        // are missing
-        if (mRequiredAccounts.size() != foundAccounts.size()) {
-            mRequiredAccounts.removeAll(foundAccounts);
-            StringBuilder sb = new StringBuilder("Device missing these accounts:");
-            for (String account : mRequiredAccounts) {
-                sb.append(' ');
-                sb.append(account);
-            }
-            fail(sb.toString());
-        }
-    }
-
-    private void closeApp(String appName, boolean forceStopApp) {
-        Intent homeIntent = new Intent(Intent.ACTION_MAIN);
-        homeIntent.addCategory(Intent.CATEGORY_HOME);
-        homeIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
-                | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
-        getInstrumentation().getContext().startActivity(homeIntent);
-        sleep(POST_LAUNCH_IDLE_TIMEOUT);
-        if (forceStopApp) {
-            Intent startIntent = mNameToIntent.get(appName);
-            if (startIntent != null) {
-                String packageName = startIntent.getComponent().getPackageName();
-                try {
-                    mAm.forceStopPackage(packageName, UserHandle.USER_CURRENT);
-                } catch (RemoteException e) {
-                    Log.w(TAG, "Error closing app", e);
-                }
-            }
-        }
-    }
-
-    private void sleep(int time) {
-        try {
-            Thread.sleep(time);
-        } catch (InterruptedException e) {
-            // ignore
-        }
-    }
-
-    private void reportError(String appName, String processName) {
-        ActivityManager am = (ActivityManager) getInstrumentation()
-                .getContext().getSystemService(Context.ACTIVITY_SERVICE);
-        List<ProcessErrorStateInfo> crashes = am.getProcessesInErrorState();
-        if (crashes != null) {
-            for (ProcessErrorStateInfo crash : crashes) {
-                if (!crash.processName.equals(processName))
-                    continue;
-
-                Log.w(TAG, appName + " crashed: " + crash.shortMsg);
-                mResult.putString(mNameToResultKey.get(appName), crash.shortMsg);
-                return;
-            }
-        }
-
-        mResult.putString(mNameToResultKey.get(appName),
-                "Crashed for unknown reason");
-        Log.w(TAG, appName
-                + " not found in process list, most likely it is crashed");
-    }
-
-    private class LaunchOrder {
-        private String mApp;
-        private String mCompilerFilter;
-        private String mLaunchReason;
-
-        LaunchOrder(String app, String compilerFilter, String launchReason){
-            mApp = app;
-            mCompilerFilter = compilerFilter;
-            mLaunchReason = launchReason;
-        }
-
-        public String getApp() {
-            return mApp;
-        }
-
-        public void setApp(String app) {
-            mApp = app;
-        }
-
-        public String getCompilerFilter() {
-            return mCompilerFilter;
-        }
-
-        public String getLaunchReason() {
-            return mLaunchReason;
-        }
-
-        public void setLaunchReason(String launchReason) {
-            mLaunchReason = launchReason;
-        }
-    }
-
-    private class AppLaunchResult {
-        long mLaunchTime;
-        long mCpuCycles;
-        long mMajorFaults;
-
-        AppLaunchResult() {
-            mLaunchTime = -1L;
-            mCpuCycles = -1L;
-            mMajorFaults = -1L;
-        }
-
-        AppLaunchResult(String launchTime, String cpuCycles, String majorFaults) {
-            try {
-                mLaunchTime = Long.parseLong(launchTime, 10);
-                mCpuCycles = Long.parseLong(cpuCycles, 10);
-                mMajorFaults = Long.parseLong(majorFaults, 10);
-            } catch (NumberFormatException e) {
-                Log.e(TAG, "Error parsing result", e);
-            }
-        }
-    }
-
-    private class AppLaunchRunnable implements Runnable {
-        private Intent mLaunchIntent;
-        private AppLaunchResult mLaunchResult;
-        private boolean mForceStopBeforeLaunch;
-        private String mLaunchReason;
-
-        public AppLaunchRunnable(Intent intent, boolean forceStopBeforeLaunch,
-                String launchReason) {
-            mLaunchIntent = intent;
-            mForceStopBeforeLaunch = forceStopBeforeLaunch;
-            mLaunchReason = launchReason;
-            mLaunchResult = new AppLaunchResult();
-        }
-
-        public AppLaunchResult getResult() {
-            return mLaunchResult;
-        }
-
-        public void run() {
-            File launchFile = null;
-            try {
-                String packageName = mLaunchIntent.getComponent().getPackageName();
-                String componentName = mLaunchIntent.getComponent().flattenToShortString();
-                if (mForceStopBeforeLaunch) {
-                    Log.v(TAG, "Stopping app before launch");
-                    mAm.forceStopPackage(packageName, UserHandle.USER_CURRENT);
-                } else {
-                    Log.v(TAG, "Not killing app. Going to Home Screen.");
-                    ParcelFileDescriptor goHome = getInstrumentation().getUiAutomation()
-                        .executeShellCommand("input keyevent 3");
-                }
-                String launchCmd = String.format("%s %s", APP_LAUNCH_CMD, componentName);
-                if (mSimplePerfAppOnly) {
-                    try {
-                        // executeShellCommand cannot handle shell specific actions, like '&'.
-                        // Therefore, we create a file containing the command and make that
-                        // the command to launch.
-                        launchFile = File.createTempFile(LAUNCH_SCRIPT_NAME, ".sh");
-                        launchFile.setExecutable(true);
-                        try (FileOutputStream stream = new FileOutputStream(launchFile);
-                             BufferedWriter writer =
-                                new BufferedWriter(new OutputStreamWriter(stream))) {
-                            String cmd = String.format(SIMPLEPERF_APP_CMD, packageName, launchCmd);
-                            writer.write(cmd);
-                        }
-                        launchCmd = launchFile.getAbsolutePath();
-                    } catch (IOException e) {
-                        Log.w(TAG, "Error writing the launch command", e);
-                        return;
-                    }
-                } else if (null != mSimplePerfCmd) {
-                    launchCmd = String.format("%s %s", mSimplePerfCmd, launchCmd);
-                }
-                Log.v(TAG, "Final launch cmd:" + launchCmd);
-                ParcelFileDescriptor parcelDesc = getInstrumentation().getUiAutomation()
-                        .executeShellCommand(launchCmd);
-                mLaunchResult = parseLaunchTimeAndWrite(parcelDesc, String.format
-                        ("App Launch :%s %s", componentName, mLaunchReason));
-            } catch (RemoteException e) {
-                Log.w(TAG, "Error launching app", e);
-            } finally {
-                if (launchFile != null) {
-                    launchFile.delete();
-                }
-            }
-        }
-
-        /**
-         * Method to parse the launch time info and write the result to file
-         *
-         * @param parcelDesc
-         * @return
-         */
-        private AppLaunchResult parseLaunchTimeAndWrite(ParcelFileDescriptor parcelDesc,
-                String headerInfo) {
-            String launchTime = "-1";
-            String cpuCycles = "-1";
-            String majorFaults = "-1";
-            boolean launchSuccess = false;
-            try {
-                InputStream inputStream = new FileInputStream(parcelDesc.getFileDescriptor());
-                /* SAMPLE OUTPUT :
-                Starting: Intent { cmp=com.google.android.calculator/com.android.calculator2.Calculator }
-                Status: ok
-                Activity: com.google.android.calculator/com.android.calculator2.Calculator
-                ThisTime: 357
-                TotalTime: 357
-                WaitTime: 377
-                Complete*/
-                /* WHEN NOT KILLING HOME :
-                Starting: Intent { cmp=com.google.android.wearable.app/
-                    com.google.android.clockwork.home.calendar.AgendaActivity }
-                Warning: Activity not started, its current task has been brought to the front
-                Status: ok
-                Activity: com.google.android.wearable.app/
-                    com.google.android.clockwork.home.calendar.AgendaActivity
-                ThisTime: 209
-                TotalTime: 209
-                WaitTime: 285
-                Complete*/
-                /* WITH SIMPLEPERF :
-                Performance counter statistics,
-                6595722690,cpu-cycles,4.511040,GHz,(100%),
-                0,major-faults,0.000,/sec,(100%),
-                Total test time,1.462129,seconds,*/
-                BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(
-                        inputStream));
-                String line = null;
-                int lineCount = 1;
-                int addLineForWarning = 0;
-                mBufferedWriter.newLine();
-                mBufferedWriter.write(headerInfo);
-                mBufferedWriter.newLine();
-                while ((line = bufferedReader.readLine()) != null) {
-                    if (lineCount == 2 && line.contains(WARNING_MESSAGE)) {
-                        addLineForWarning = 1;
-                    }
-                    if (lineCount == (2 + addLineForWarning) && line.contains(SUCCESS_MESSAGE)) {
-                        launchSuccess = true;
-                    }
-                    // Parse TotalTime which is the launch time
-                    if (launchSuccess && lineCount == (5 + addLineForWarning)) {
-                        String launchSplit[] = line.split(":");
-                        launchTime = launchSplit[1].trim();
-                    }
-
-                    if (mSimplePerfAppOnly) {
-                        // Parse simpleperf output.
-                        if (lineCount == (9 + addLineForWarning)) {
-                            if (!line.contains("cpu-cycles")) {
-                                Log.e(TAG, "Error in simpleperf output");
-                            } else {
-                                cpuCycles = line.split(",")[0].trim();
-                            }
-                        } else if (lineCount == (10 + addLineForWarning)) {
-                            if (!line.contains("major-faults")) {
-                                Log.e(TAG, "Error in simpleperf output");
-                            } else {
-                                majorFaults = line.split(",")[0].trim();
-                            }
-                        }
-                    }
-                    mBufferedWriter.write(line);
-                    mBufferedWriter.newLine();
-                    lineCount++;
-                }
-                mBufferedWriter.flush();
-                inputStream.close();
-            } catch (IOException e) {
-                Log.w(TAG, "Error writing the launch file", e);
-            }
-            return new AppLaunchResult(launchTime, cpuCycles, majorFaults);
-        }
-
-    }
-}
diff --git a/tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkTemplateTest.java b/tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkTemplateTest.java
index d03aee2..4a724b7 100644
--- a/tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkTemplateTest.java
+++ b/tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkTemplateTest.java
@@ -15,12 +15,13 @@
  */
 package android.net.vcn;
 
+import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_ANY;
+import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_FORBIDDEN;
+import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_REQUIRED;
 import static android.net.vcn.VcnUnderlyingNetworkTemplate.NETWORK_QUALITY_ANY;
 import static android.net.vcn.VcnUnderlyingNetworkTemplate.NETWORK_QUALITY_OK;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
 
 import org.junit.Test;
 
@@ -32,26 +33,26 @@
     private static final Set<Integer> ALLOWED_CARRIER_IDS = new HashSet<>();
 
     // Package private for use in VcnGatewayConnectionConfigTest
-    static VcnCellUnderlyingNetworkTemplate getTestNetworkPriority() {
+    static VcnCellUnderlyingNetworkTemplate getTestNetworkTemplate() {
         return new VcnCellUnderlyingNetworkTemplate.Builder()
                 .setNetworkQuality(NETWORK_QUALITY_OK)
-                .setAllowMetered(true /* allowMetered */)
-                .setAllowedOperatorPlmnIds(ALLOWED_PLMN_IDS)
-                .setAllowedSpecificCarrierIds(ALLOWED_CARRIER_IDS)
-                .setAllowRoaming(true /* allowRoaming */)
-                .setRequireOpportunistic(true /* requireOpportunistic */)
+                .setMetered(MATCH_FORBIDDEN)
+                .setOperatorPlmnIds(ALLOWED_PLMN_IDS)
+                .setSimSpecificCarrierIds(ALLOWED_CARRIER_IDS)
+                .setRoaming(MATCH_FORBIDDEN)
+                .setOpportunistic(MATCH_REQUIRED)
                 .build();
     }
 
     @Test
     public void testBuilderAndGetters() {
-        final VcnCellUnderlyingNetworkTemplate networkPriority = getTestNetworkPriority();
+        final VcnCellUnderlyingNetworkTemplate networkPriority = getTestNetworkTemplate();
         assertEquals(NETWORK_QUALITY_OK, networkPriority.getNetworkQuality());
-        assertTrue(networkPriority.allowMetered());
-        assertEquals(ALLOWED_PLMN_IDS, networkPriority.getAllowedOperatorPlmnIds());
-        assertEquals(ALLOWED_CARRIER_IDS, networkPriority.getAllowedSpecificCarrierIds());
-        assertTrue(networkPriority.allowRoaming());
-        assertTrue(networkPriority.requireOpportunistic());
+        assertEquals(MATCH_FORBIDDEN, networkPriority.getMetered());
+        assertEquals(ALLOWED_PLMN_IDS, networkPriority.getOperatorPlmnIds());
+        assertEquals(ALLOWED_CARRIER_IDS, networkPriority.getSimSpecificCarrierIds());
+        assertEquals(MATCH_FORBIDDEN, networkPriority.getRoaming());
+        assertEquals(MATCH_REQUIRED, networkPriority.getOpportunistic());
     }
 
     @Test
@@ -59,16 +60,16 @@
         final VcnCellUnderlyingNetworkTemplate networkPriority =
                 new VcnCellUnderlyingNetworkTemplate.Builder().build();
         assertEquals(NETWORK_QUALITY_ANY, networkPriority.getNetworkQuality());
-        assertFalse(networkPriority.allowMetered());
-        assertEquals(new HashSet<String>(), networkPriority.getAllowedOperatorPlmnIds());
-        assertEquals(new HashSet<Integer>(), networkPriority.getAllowedSpecificCarrierIds());
-        assertFalse(networkPriority.allowRoaming());
-        assertFalse(networkPriority.requireOpportunistic());
+        assertEquals(MATCH_ANY, networkPriority.getMetered());
+        assertEquals(new HashSet<String>(), networkPriority.getOperatorPlmnIds());
+        assertEquals(new HashSet<Integer>(), networkPriority.getSimSpecificCarrierIds());
+        assertEquals(MATCH_ANY, networkPriority.getRoaming());
+        assertEquals(MATCH_ANY, networkPriority.getOpportunistic());
     }
 
     @Test
     public void testPersistableBundle() {
-        final VcnCellUnderlyingNetworkTemplate networkPriority = getTestNetworkPriority();
+        final VcnCellUnderlyingNetworkTemplate networkPriority = getTestNetworkTemplate();
         assertEquals(
                 networkPriority,
                 VcnUnderlyingNetworkTemplate.fromPersistableBundle(
diff --git a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java
index 1f2905d..2aef9ae 100644
--- a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java
+++ b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java
@@ -17,8 +17,8 @@
 package android.net.vcn;
 
 import static android.net.ipsec.ike.IkeSessionParams.IKE_OPTION_MOBIKE;
-import static android.net.vcn.VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_PRIORITIES;
-import static android.net.vcn.VcnGatewayConnectionConfig.UNDERLYING_NETWORK_PRIORITIES_KEY;
+import static android.net.vcn.VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES;
+import static android.net.vcn.VcnGatewayConnectionConfig.UNDERLYING_NETWORK_TEMPLATES_KEY;
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
@@ -40,8 +40,9 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.LinkedHashSet;
+import java.util.List;
 import java.util.concurrent.TimeUnit;
 
 @RunWith(AndroidJUnit4.class)
@@ -54,17 +55,17 @@
             };
     public static final int[] UNDERLYING_CAPS = new int[] {NetworkCapabilities.NET_CAPABILITY_DUN};
 
-    private static final LinkedHashSet<VcnUnderlyingNetworkTemplate> UNDERLYING_NETWORK_PRIORITIES =
-            new LinkedHashSet();
+    private static final List<VcnUnderlyingNetworkTemplate> UNDERLYING_NETWORK_TEMPLATES =
+            new ArrayList();
 
     static {
         Arrays.sort(EXPOSED_CAPS);
         Arrays.sort(UNDERLYING_CAPS);
 
-        UNDERLYING_NETWORK_PRIORITIES.add(
-                VcnCellUnderlyingNetworkTemplateTest.getTestNetworkPriority());
-        UNDERLYING_NETWORK_PRIORITIES.add(
-                VcnWifiUnderlyingNetworkTemplateTest.getTestNetworkPriority());
+        UNDERLYING_NETWORK_TEMPLATES.add(
+                VcnCellUnderlyingNetworkTemplateTest.getTestNetworkTemplate());
+        UNDERLYING_NETWORK_TEMPLATES.add(
+                VcnWifiUnderlyingNetworkTemplateTest.getTestNetworkTemplate());
     }
 
     public static final long[] RETRY_INTERVALS_MS =
@@ -95,7 +96,7 @@
     // Public for use in VcnGatewayConnectionTest
     public static VcnGatewayConnectionConfig buildTestConfig() {
         final VcnGatewayConnectionConfig.Builder builder =
-                newBuilder().setVcnUnderlyingNetworkPriorities(UNDERLYING_NETWORK_PRIORITIES);
+                newBuilder().setVcnUnderlyingNetworkPriorities(UNDERLYING_NETWORK_TEMPLATES);
 
         return buildTestConfigWithExposedCaps(builder, EXPOSED_CAPS);
     }
@@ -174,10 +175,10 @@
     }
 
     @Test
-    public void testBuilderRequiresNonNullNetworkPriorities() {
+    public void testBuilderRequiresNonNullNetworkTemplates() {
         try {
             newBuilder().setVcnUnderlyingNetworkPriorities(null);
-            fail("Expected exception due to invalid underlyingNetworkPriorities");
+            fail("Expected exception due to invalid underlyingNetworkTemplates");
         } catch (NullPointerException e) {
         }
     }
@@ -219,7 +220,7 @@
         Arrays.sort(exposedCaps);
         assertArrayEquals(EXPOSED_CAPS, exposedCaps);
 
-        assertEquals(UNDERLYING_NETWORK_PRIORITIES, config.getVcnUnderlyingNetworkPriorities());
+        assertEquals(UNDERLYING_NETWORK_TEMPLATES, config.getVcnUnderlyingNetworkPriorities());
         assertEquals(TUNNEL_CONNECTION_PARAMS, config.getTunnelConnectionParams());
 
         assertArrayEquals(RETRY_INTERVALS_MS, config.getRetryIntervalsMillis());
@@ -234,13 +235,13 @@
     }
 
     @Test
-    public void testParsePersistableBundleWithoutVcnUnderlyingNetworkPriorities() {
+    public void testParsePersistableBundleWithoutVcnUnderlyingNetworkTemplates() {
         PersistableBundle configBundle = buildTestConfig().toPersistableBundle();
-        configBundle.putPersistableBundle(UNDERLYING_NETWORK_PRIORITIES_KEY, null);
+        configBundle.putPersistableBundle(UNDERLYING_NETWORK_TEMPLATES_KEY, null);
 
         final VcnGatewayConnectionConfig config = new VcnGatewayConnectionConfig(configBundle);
         assertEquals(
-                DEFAULT_UNDERLYING_NETWORK_PRIORITIES, config.getVcnUnderlyingNetworkPriorities());
+                DEFAULT_UNDERLYING_NETWORK_TEMPLATES, config.getVcnUnderlyingNetworkPriorities());
     }
 
     private static IkeTunnelConnectionParams buildTunnelConnectionParams(String ikePsk) {
@@ -285,39 +286,36 @@
         assertNotEquals(config, anotherConfig);
     }
 
-    private static VcnGatewayConnectionConfig buildTestConfigWithVcnUnderlyingNetworkPriorities(
-            LinkedHashSet<VcnUnderlyingNetworkTemplate> networkPriorities) {
+    private static VcnGatewayConnectionConfig buildTestConfigWithVcnUnderlyingNetworkTemplates(
+            List<VcnUnderlyingNetworkTemplate> networkTemplates) {
         return buildTestConfigWithExposedCaps(
                 new VcnGatewayConnectionConfig.Builder(
-                                "buildTestConfigWithVcnUnderlyingNetworkPriorities",
+                                "buildTestConfigWithVcnUnderlyingNetworkTemplates",
                                 TUNNEL_CONNECTION_PARAMS)
-                        .setVcnUnderlyingNetworkPriorities(networkPriorities),
+                        .setVcnUnderlyingNetworkPriorities(networkTemplates),
                 EXPOSED_CAPS);
     }
 
     @Test
-    public void testVcnUnderlyingNetworkPrioritiesEquality() throws Exception {
+    public void testVcnUnderlyingNetworkTemplatesEquality() throws Exception {
         final VcnGatewayConnectionConfig config =
-                buildTestConfigWithVcnUnderlyingNetworkPriorities(UNDERLYING_NETWORK_PRIORITIES);
+                buildTestConfigWithVcnUnderlyingNetworkTemplates(UNDERLYING_NETWORK_TEMPLATES);
 
-        final LinkedHashSet<VcnUnderlyingNetworkTemplate> networkPrioritiesEqual =
-                new LinkedHashSet();
-        networkPrioritiesEqual.add(VcnCellUnderlyingNetworkTemplateTest.getTestNetworkPriority());
-        networkPrioritiesEqual.add(VcnWifiUnderlyingNetworkTemplateTest.getTestNetworkPriority());
+        final List<VcnUnderlyingNetworkTemplate> networkTemplatesEqual = new ArrayList();
+        networkTemplatesEqual.add(VcnCellUnderlyingNetworkTemplateTest.getTestNetworkTemplate());
+        networkTemplatesEqual.add(VcnWifiUnderlyingNetworkTemplateTest.getTestNetworkTemplate());
         final VcnGatewayConnectionConfig configEqual =
-                buildTestConfigWithVcnUnderlyingNetworkPriorities(networkPrioritiesEqual);
+                buildTestConfigWithVcnUnderlyingNetworkTemplates(networkTemplatesEqual);
 
-        final LinkedHashSet<VcnUnderlyingNetworkTemplate> networkPrioritiesNotEqual =
-                new LinkedHashSet();
-        networkPrioritiesNotEqual.add(
-                VcnWifiUnderlyingNetworkTemplateTest.getTestNetworkPriority());
+        final List<VcnUnderlyingNetworkTemplate> networkTemplatesNotEqual = new ArrayList();
+        networkTemplatesNotEqual.add(VcnWifiUnderlyingNetworkTemplateTest.getTestNetworkTemplate());
         final VcnGatewayConnectionConfig configNotEqual =
-                buildTestConfigWithVcnUnderlyingNetworkPriorities(networkPrioritiesNotEqual);
+                buildTestConfigWithVcnUnderlyingNetworkTemplates(networkTemplatesNotEqual);
 
-        assertEquals(UNDERLYING_NETWORK_PRIORITIES, networkPrioritiesEqual);
+        assertEquals(UNDERLYING_NETWORK_TEMPLATES, networkTemplatesEqual);
         assertEquals(config, configEqual);
 
-        assertNotEquals(UNDERLYING_NETWORK_PRIORITIES, networkPrioritiesNotEqual);
+        assertNotEquals(UNDERLYING_NETWORK_TEMPLATES, networkTemplatesNotEqual);
         assertNotEquals(config, configNotEqual);
     }
 }
diff --git a/tests/vcn/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplateTest.java b/tests/vcn/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplateTest.java
index 652057f..cb5b47b 100644
--- a/tests/vcn/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplateTest.java
+++ b/tests/vcn/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplateTest.java
@@ -15,36 +15,38 @@
  */
 package android.net.vcn;
 
+import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_ANY;
+import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_FORBIDDEN;
 import static android.net.vcn.VcnUnderlyingNetworkTemplate.NETWORK_QUALITY_ANY;
 import static android.net.vcn.VcnUnderlyingNetworkTemplate.NETWORK_QUALITY_OK;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 import org.junit.Test;
 
+import java.util.Set;
+
 public class VcnWifiUnderlyingNetworkTemplateTest {
     private static final String SSID = "TestWifi";
     private static final int INVALID_NETWORK_QUALITY = -1;
 
     // Package private for use in VcnGatewayConnectionConfigTest
-    static VcnWifiUnderlyingNetworkTemplate getTestNetworkPriority() {
+    static VcnWifiUnderlyingNetworkTemplate getTestNetworkTemplate() {
         return new VcnWifiUnderlyingNetworkTemplate.Builder()
                 .setNetworkQuality(NETWORK_QUALITY_OK)
-                .setAllowMetered(true /* allowMetered */)
-                .setSsid(SSID)
+                .setMetered(MATCH_FORBIDDEN)
+                .setSsids(Set.of(SSID))
                 .build();
     }
 
     @Test
     public void testBuilderAndGetters() {
-        final VcnWifiUnderlyingNetworkTemplate networkPriority = getTestNetworkPriority();
+        final VcnWifiUnderlyingNetworkTemplate networkPriority = getTestNetworkTemplate();
         assertEquals(NETWORK_QUALITY_OK, networkPriority.getNetworkQuality());
-        assertTrue(networkPriority.allowMetered());
-        assertEquals(SSID, networkPriority.getSsid());
+        assertEquals(MATCH_FORBIDDEN, networkPriority.getMetered());
+        assertEquals(Set.of(SSID), networkPriority.getSsids());
     }
 
     @Test
@@ -52,8 +54,8 @@
         final VcnWifiUnderlyingNetworkTemplate networkPriority =
                 new VcnWifiUnderlyingNetworkTemplate.Builder().build();
         assertEquals(NETWORK_QUALITY_ANY, networkPriority.getNetworkQuality());
-        assertFalse(networkPriority.allowMetered());
-        assertNull(SSID, networkPriority.getSsid());
+        assertEquals(MATCH_ANY, networkPriority.getMetered());
+        assertTrue(networkPriority.getSsids().isEmpty());
     }
 
     @Test
@@ -68,7 +70,7 @@
 
     @Test
     public void testPersistableBundle() {
-        final VcnWifiUnderlyingNetworkTemplate networkPriority = getTestNetworkPriority();
+        final VcnWifiUnderlyingNetworkTemplate networkPriority = getTestNetworkTemplate();
         assertEquals(
                 networkPriority,
                 VcnUnderlyingNetworkTemplate.fromPersistableBundle(
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java
index f23d5bf..4bb7de8 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.vcn.routeselection;
 
+import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_FORBIDDEN;
+import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_REQUIRED;
 import static android.net.vcn.VcnUnderlyingNetworkTemplate.NETWORK_QUALITY_OK;
 
 import static com.android.server.vcn.VcnTestUtils.setupSystemService;
@@ -145,7 +147,7 @@
         final VcnWifiUnderlyingNetworkTemplate wifiNetworkPriority =
                 new VcnWifiUnderlyingNetworkTemplate.Builder()
                         .setNetworkQuality(NETWORK_QUALITY_OK)
-                        .setAllowMetered(false /* allowMetered */)
+                        .setMetered(MATCH_FORBIDDEN)
                         .build();
 
         assertFalse(
@@ -164,7 +166,6 @@
         final VcnWifiUnderlyingNetworkTemplate wifiNetworkPriority =
                 new VcnWifiUnderlyingNetworkTemplate.Builder()
                         .setNetworkQuality(NETWORK_QUALITY_OK)
-                        .setAllowMetered(true /* allowMetered */)
                         .build();
         final UnderlyingNetworkRecord selectedNetworkRecord =
                 isSelectedNetwork ? mWifiNetworkRecord : null;
@@ -214,8 +215,7 @@
         final VcnWifiUnderlyingNetworkTemplate wifiNetworkPriority =
                 new VcnWifiUnderlyingNetworkTemplate.Builder()
                         .setNetworkQuality(NETWORK_QUALITY_OK)
-                        .setAllowMetered(true /* allowMetered */)
-                        .setSsid(nwPrioritySsid)
+                        .setSsids(Set.of(nwPrioritySsid))
                         .build();
 
         assertEquals(
@@ -238,10 +238,7 @@
     }
 
     private static VcnCellUnderlyingNetworkTemplate.Builder getCellNetworkPriorityBuilder() {
-        return new VcnCellUnderlyingNetworkTemplate.Builder()
-                .setNetworkQuality(NETWORK_QUALITY_OK)
-                .setAllowMetered(true /* allowMetered */)
-                .setAllowRoaming(true /* allowRoaming */);
+        return new VcnCellUnderlyingNetworkTemplate.Builder().setNetworkQuality(NETWORK_QUALITY_OK);
     }
 
     @Test
@@ -258,9 +255,7 @@
     @Test
     public void testMatchOpportunisticCell() {
         final VcnCellUnderlyingNetworkTemplate opportunisticCellNetworkPriority =
-                getCellNetworkPriorityBuilder()
-                        .setRequireOpportunistic(true /* requireOpportunistic */)
-                        .build();
+                getCellNetworkPriorityBuilder().setOpportunistic(MATCH_REQUIRED).build();
 
         when(mSubscriptionSnapshot.isOpportunistic(SUB_ID)).thenReturn(true);
         when(mSubscriptionSnapshot.getAllSubIdsInGroup(SUB_GROUP)).thenReturn(new ArraySet<>());
@@ -279,7 +274,7 @@
         final String networkPriorityPlmnId = useMatchedPlmnId ? PLMN_ID : PLMN_ID_OTHER;
         final VcnCellUnderlyingNetworkTemplate networkPriority =
                 getCellNetworkPriorityBuilder()
-                        .setAllowedOperatorPlmnIds(Set.of(networkPriorityPlmnId))
+                        .setOperatorPlmnIds(Set.of(networkPriorityPlmnId))
                         .build();
 
         assertEquals(
@@ -308,7 +303,7 @@
         final int networkPriorityCarrierId = useMatchedCarrierId ? CARRIER_ID : CARRIER_ID_OTHER;
         final VcnCellUnderlyingNetworkTemplate networkPriority =
                 getCellNetworkPriorityBuilder()
-                        .setAllowedSpecificCarrierIds(Set.of(networkPriorityCarrierId))
+                        .setSimSpecificCarrierIds(Set.of(networkPriorityCarrierId))
                         .build();
 
         assertEquals(
@@ -336,7 +331,7 @@
     @Test
     public void testMatchWifiFailWithoutNotRoamingBit() {
         final VcnCellUnderlyingNetworkTemplate networkPriority =
-                getCellNetworkPriorityBuilder().setAllowRoaming(false /* allowRoaming */).build();
+                getCellNetworkPriorityBuilder().setRoaming(MATCH_FORBIDDEN).build();
 
         assertFalse(
                 checkMatchesCellPriorityRule(
@@ -353,7 +348,7 @@
                 calculatePriorityClass(
                         mVcnContext,
                         networkRecord,
-                        VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_PRIORITIES,
+                        VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES,
                         SUB_GROUP,
                         mSubscriptionSnapshot,
                         null /* currentlySelected */,