Merge "Remove text and animated dots on wireless charging animation." into sc-dev am: 4e784c87b1

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/14064915

Change-Id: I5acebd7e19ae439fd797050d7c30700ebcb5b2f3
diff --git a/Android.bp b/Android.bp
index 9e1820e..cf1210c 100644
--- a/Android.bp
+++ b/Android.bp
@@ -388,6 +388,8 @@
     ],
     sdk_version: "core_platform",
     static_libs: [
+        // TODO(b/184162091)
+        "android.hardware.soundtrigger3-V1-java",
         "bouncycastle-repackaged-unbundled",
         "framework-internal-utils",
         // If MimeMap ever becomes its own APEX, then this dependency would need to be removed
diff --git a/core/api/current.txt b/core/api/current.txt
index 1f7da09..43c4a37 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -12536,6 +12536,7 @@
     method public abstract int getInstantAppCookieMaxBytes();
     method @NonNull public abstract android.content.pm.InstrumentationInfo getInstrumentationInfo(@NonNull android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method @Nullable public abstract android.content.Intent getLaunchIntentForPackage(@NonNull String);
+    method @NonNull public android.content.IntentSender getLaunchIntentSenderForPackage(@NonNull String);
     method @Nullable public abstract android.content.Intent getLeanbackLaunchIntentForPackage(@NonNull String);
     method @NonNull public java.util.Set<java.lang.String> getMimeGroup(@NonNull String);
     method @NonNull public android.content.pm.ModuleInfo getModuleInfo(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -22516,7 +22517,6 @@
     field public static final String KEY_ROTATION = "rotation-degrees";
     field public static final String KEY_SAMPLE_RATE = "sample-rate";
     field public static final String KEY_SLICE_HEIGHT = "slice-height";
-    field public static final String KEY_SLOW_MOTION_MARKERS = "slow-motion-markers";
     field public static final String KEY_STRIDE = "stride";
     field public static final String KEY_TEMPORAL_LAYERING = "ts-schema";
     field public static final String KEY_TILE_HEIGHT = "tile-height";
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 5242782..ac09d0b 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -9061,6 +9061,7 @@
     field public static final String NAMESPACE_STATSD_NATIVE_BOOT = "statsd_native_boot";
     field @Deprecated public static final String NAMESPACE_STORAGE = "storage";
     field public static final String NAMESPACE_STORAGE_NATIVE_BOOT = "storage_native_boot";
+    field public static final String NAMESPACE_SWCODEC_NATIVE = "swcodec_native";
     field public static final String NAMESPACE_SYSTEMUI = "systemui";
     field public static final String NAMESPACE_SYSTEM_TIME = "system_time";
     field public static final String NAMESPACE_TELEPHONY = "telephony";
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 63e9010..501dd43 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1513,6 +1513,11 @@
     method @NonNull public android.media.audiopolicy.AudioPolicy.Builder setIsTestFocusPolicy(boolean);
   }
 
+  public final class AudioProductStrategy implements android.os.Parcelable {
+    method public int getLegacyStreamTypeForAudioAttributes(@NonNull android.media.AudioAttributes);
+    method public int getVolumeGroupIdForAudioAttributes(@NonNull android.media.AudioAttributes);
+  }
+
 }
 
 package android.media.metrics {
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index dba62b9..34401eb 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -313,6 +313,16 @@
     }
 
     @Override
+    public @NonNull IntentSender getLaunchIntentSenderForPackage(@NonNull String packageName) {
+        try {
+            return mPM.getLaunchIntentSenderForPackage(packageName, mContext.getPackageName(),
+                    mContext.getAttributionTag(), getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
     public int[] getPackageGids(String packageName) throws NameNotFoundException {
         return getPackageGids(packageName, 0);
     }
diff --git a/core/java/android/companion/Association.java b/core/java/android/companion/Association.java
index 9007d9d..b060ce2 100644
--- a/core/java/android/companion/Association.java
+++ b/core/java/android/companion/Association.java
@@ -53,8 +53,6 @@
 
 
 
-
-
     // Code below generated by codegen v1.0.22.
     //
     // DO NOT MODIFY!
@@ -242,7 +240,7 @@
     };
 
     @DataClass.Generated(
-            time = 1612832377589L,
+            time = 1611795283642L,
             codegenVersion = "1.0.22",
             sourceFile = "frameworks/base/core/java/android/companion/Association.java",
             inputSignatures = "private final @android.annotation.UserIdInt int mUserId\nprivate final @android.annotation.NonNull java.lang.String mDeviceMacAddress\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.Nullable java.lang.String mDeviceProfile\nprivate final  boolean mNotifyOnDeviceNearby\nprivate final  long mTimeApprovedMs\npublic  int getUserId()\nprivate  java.lang.String timeApprovedMsToString()\nclass Association extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genHiddenConstructor=true)")
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index a482b8c..c2a9b81 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1804,7 +1804,7 @@
      * the package name of the current installed package to be uninstalled.
      * You can optionally supply {@link #EXTRA_RETURN_RESULT}.
      * <p>
-     * Output: If {@link #EXTRA_RETURN_RESULT}, returns whether the install
+     * Output: If {@link #EXTRA_RETURN_RESULT}, returns whether the uninstall
      * succeeded.
      * <p>
      * Requires {@link android.Manifest.permission#REQUEST_DELETE_PACKAGES}
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 7fe2a41..8cd622e 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -757,6 +757,9 @@
 
     void requestChecksums(in String packageName, boolean includeSplits, int optional, int required, in List trustedInstallers, in IOnChecksumsReadyListener onChecksumsReadyListener, int userId);
 
+    IntentSender getLaunchIntentSenderForPackage(String packageName, String callingPackage,
+                String featureId, int userId);
+
     //------------------------------------------------------------------------
     //
     // The following binder interfaces have been moved to IPermissionManager
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 21de365..12bf738 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -4522,12 +4522,17 @@
      * main activity in the category {@link Intent#CATEGORY_LAUNCHER}. Returns
      * <code>null</code> if neither are found.
      *
+     * <p>Consider using {@link #getLaunchIntentSenderForPackage(String)} if
+     * the caller is not allowed to query for the <code>packageName</code>.
+     *
      * @param packageName The name of the package to inspect.
      *
      * @return A fully-qualified {@link Intent} that can be used to launch the
      * main activity in the package. Returns <code>null</code> if the package
      * does not contain such an activity, or if <em>packageName</em> is not
      * recognized.
+     *
+     * @see #getLaunchIntentSenderForPackage(String)
      */
     public abstract @Nullable Intent getLaunchIntentForPackage(@NonNull String packageName);
 
@@ -4562,6 +4567,28 @@
     public abstract @Nullable Intent getCarLaunchIntentForPackage(@NonNull String packageName);
 
     /**
+     * Returns an {@link IntentSender} that can be used to launch a front-door activity in a
+     * package. This is used, for example, to implement an "open" button when browsing through
+     * packages. The current implementation is the same with
+     * {@link #getLaunchIntentForPackage(String)}. Instead of returning the {@link Intent}, it
+     * returns the {@link IntentSender} which is not restricted by the package visibility.
+     *
+     * <p>The caller can invoke
+     * {@link IntentSender#sendIntent(Context, int, Intent, IntentSender.OnFinished, Handler)}
+     * to launch the activity. An {@link IntentSender.SendIntentException} is thrown if the
+     * package does not contain such an activity, or if <em>packageName</em> is not recognized.
+     *
+     * @param packageName The name of the package to inspect.
+     * @return Returns a {@link IntentSender} to launch the activity.
+     *
+     * @see #getLaunchIntentForPackage(String)
+     */
+    public @NonNull IntentSender getLaunchIntentSenderForPackage(@NonNull String packageName) {
+        throw new UnsupportedOperationException("getLaunchIntentSenderForPackage not implemented"
+                + "in subclass");
+    }
+
+    /**
      * Return an array of all of the POSIX secondary group IDs that have been
      * assigned to the given package.
      * <p>
@@ -6825,7 +6852,7 @@
     @NonNull
     public Resources getResourcesForApplication(@NonNull ApplicationInfo app, @Nullable
             Configuration configuration) throws NameNotFoundException {
-        throw new UnsupportedOperationException();
+        return getResourcesForApplication(app);
     }
 
     /**
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 5ff1124..ad149e1 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -885,7 +885,11 @@
         if ((flags & PackageManager.GET_SIGNING_CERTIFICATES) != 0) {
             if (p.mSigningDetails != SigningDetails.UNKNOWN) {
                 // only return a valid SigningInfo if there is signing information to report
-                pi.signingInfo = new SigningInfo(p.mSigningDetails);
+                pi.signingInfo = new SigningInfo(
+                        new android.content.pm.SigningDetails(p.mSigningDetails.signatures,
+                                p.mSigningDetails.signatureSchemeVersion,
+                                p.mSigningDetails.publicKeys,
+                                p.mSigningDetails.pastSigningCertificates));
             } else {
                 pi.signingInfo = null;
             }
@@ -1400,7 +1404,7 @@
             // must use v2 signing scheme
             minSignatureScheme = SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V2;
         }
-        SigningDetails verified;
+        final android.content.pm.SigningDetails verified;
         if (skipVerify) {
             // systemDir APKs are already trusted, save time by not verifying; since the signature
             // is not verified and some system apps can have their V2+ signatures stripped allow
@@ -1415,9 +1419,13 @@
         // we encountered. Note that for splits, certificates may have
         // already been populated during an earlier parse of a base APK.
         if (pkg.mSigningDetails == SigningDetails.UNKNOWN) {
-            pkg.mSigningDetails = verified;
+            pkg.mSigningDetails = new SigningDetails(verified.getSignatures(),
+                    verified.getSignatureSchemeVersion(),
+                    verified.getPublicKeys(),
+                    verified.getPastSigningCertificates());
         } else {
-            if (!Signature.areExactMatch(pkg.mSigningDetails.signatures, verified.signatures)) {
+            if (!Signature.areExactMatch(pkg.mSigningDetails.signatures,
+                    verified.getSignatures())) {
                 throw new PackageParserException(
                         INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES,
                         apkPath + " has mismatched certificates");
diff --git a/core/java/android/content/pm/Signature.java b/core/java/android/content/pm/Signature.java
index bce4b87..3f5c5d2 100644
--- a/core/java/android/content/pm/Signature.java
+++ b/core/java/android/content/pm/Signature.java
@@ -256,7 +256,7 @@
         try {
             if (obj != null) {
                 Signature other = (Signature)obj;
-                // Note, some classes, such as PackageParser.SigningDetails, rely on equals
+                // Note, some classes, such as SigningDetails, rely on equals
                 // only comparing the mSignature arrays without the flags.
                 return this == other || Arrays.equals(mSignature, other.mSignature);
             }
diff --git a/core/java/android/content/pm/SigningDetails.java b/core/java/android/content/pm/SigningDetails.java
new file mode 100644
index 0000000..584a058
--- /dev/null
+++ b/core/java/android/content/pm/SigningDetails.java
@@ -0,0 +1,928 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.ArraySet;
+import android.util.PackageUtils;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.DataClass;
+
+import libcore.util.HexEncoding;
+
+import java.security.PublicKey;
+import java.security.cert.CertificateException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * A container for signing-related data of an application package.
+ *
+ * @hide
+ */
+@DataClass(genConstructor = false, genConstDefs = false, genParcelable = true, genAidl = false)
+public final class SigningDetails implements Parcelable {
+
+    private static final String TAG = "SigningDetails";
+
+    @IntDef({SignatureSchemeVersion.UNKNOWN,
+            SignatureSchemeVersion.JAR,
+            SignatureSchemeVersion.SIGNING_BLOCK_V2,
+            SignatureSchemeVersion.SIGNING_BLOCK_V3,
+            SignatureSchemeVersion.SIGNING_BLOCK_V4})
+    public @interface SignatureSchemeVersion {
+        int UNKNOWN = 0;
+        int JAR = 1;
+        int SIGNING_BLOCK_V2 = 2;
+        int SIGNING_BLOCK_V3 = 3;
+        int SIGNING_BLOCK_V4 = 4;
+    }
+
+    /** The signing certificates associated with this application package. */
+    private final @Nullable Signature[] mSignatures;
+
+    /** The signature scheme version for this application package. */
+    private final @SignatureSchemeVersion int mSignatureSchemeVersion;
+
+    /** The public keys set for the certificates. */
+    private final @Nullable ArraySet<PublicKey> mPublicKeys;
+
+    /**
+     * APK Signature Scheme v3 includes support for adding a proof-of-rotation record that
+     * contains two pieces of information:
+     *   1) the past signing certificates
+     *   2) the flags that APK wants to assign to each of the past signing certificates.
+     *
+     * This collection of {@code Signature} objects, each of which is formed from a former
+     * signing certificate of this APK before it was changed by signing certificate rotation,
+     * represents the first piece of information.  It is the APK saying to the rest of the
+     * world: "hey if you trust the old cert, you can trust me!"  This is useful, if for
+     * instance, the platform would like to determine whether or not to allow this APK to do
+     * something it would've allowed it to do under the old cert (like upgrade).
+     */
+    private final @Nullable Signature[] mPastSigningCertificates;
+
+    /** special value used to see if cert is in package - not exposed to callers */
+    private static final int PAST_CERT_EXISTS = 0;
+
+    @IntDef(flag = true,
+            value = {CertCapabilities.INSTALLED_DATA,
+                    CertCapabilities.SHARED_USER_ID,
+                    CertCapabilities.PERMISSION,
+                    CertCapabilities.ROLLBACK})
+    public @interface CertCapabilities {
+
+        /** accept data from already installed pkg with this cert */
+        int INSTALLED_DATA = 1;
+
+        /** accept sharedUserId with pkg with this cert */
+        int SHARED_USER_ID = 2;
+
+        /** grant SIGNATURE permissions to pkgs with this cert */
+        int PERMISSION = 4;
+
+        /** allow pkg to update to one signed by this certificate */
+        int ROLLBACK = 8;
+
+        /** allow pkg to continue to have auth access gated by this cert */
+        int AUTH = 16;
+    }
+
+    /** A representation of unknown signing details. Use instead of null. */
+    public static final SigningDetails UNKNOWN = new SigningDetails(/* signatures */ null,
+            SignatureSchemeVersion.UNKNOWN, /* keys */ null, /* pastSigningCertificates */ null);
+
+    @VisibleForTesting
+    public SigningDetails(@Nullable Signature[] signatures,
+            @SignatureSchemeVersion int signatureSchemeVersion,
+            @Nullable ArraySet<PublicKey> keys, @Nullable Signature[] pastSigningCertificates) {
+        mSignatures = signatures;
+        mSignatureSchemeVersion = signatureSchemeVersion;
+        mPublicKeys = keys;
+        mPastSigningCertificates = pastSigningCertificates;
+    }
+
+    public SigningDetails(@Nullable Signature[] signatures,
+            @SignatureSchemeVersion int signatureSchemeVersion,
+            @Nullable Signature[] pastSigningCertificates)
+            throws CertificateException {
+        this(signatures, signatureSchemeVersion, toSigningKeys(signatures),
+                pastSigningCertificates);
+    }
+
+    public SigningDetails(@Nullable Signature[] signatures,
+            @SignatureSchemeVersion int signatureSchemeVersion)
+            throws CertificateException {
+        this(signatures, signatureSchemeVersion, /* pastSigningCertificates */ null);
+    }
+
+    public SigningDetails(@Nullable SigningDetails orig) {
+        if (orig != null) {
+            if (orig.mSignatures != null) {
+                mSignatures = orig.mSignatures.clone();
+            } else {
+                mSignatures = null;
+            }
+            mSignatureSchemeVersion = orig.mSignatureSchemeVersion;
+            mPublicKeys = new ArraySet<>(orig.mPublicKeys);
+            if (orig.mPastSigningCertificates != null) {
+                mPastSigningCertificates = orig.mPastSigningCertificates.clone();
+            } else {
+                mPastSigningCertificates = null;
+            }
+        } else {
+            mSignatures = null;
+            mSignatureSchemeVersion = SignatureSchemeVersion.UNKNOWN;
+            mPublicKeys = null;
+            mPastSigningCertificates = null;
+        }
+    }
+
+    /**
+     * Merges the signing lineage of this instance with the lineage in the provided {@code
+     * otherSigningDetails} when one has the same or an ancestor signer of the other.
+     *
+     * <p>Merging two signing lineages will result in a new {@code SigningDetails} instance
+     * containing the longest common lineage with the most restrictive capabilities. If the two
+     * lineages contain the same signers with the same capabilities then the instance on which
+     * this was invoked is returned without any changes. Similarly if neither instance has a
+     * lineage, or if neither has the same or an ancestor signer then this instance is returned.
+     *
+     * Following are some example results of this method for lineages with signers A, B, C, D:
+     * - lineage B merged with lineage A -> B returns lineage A -> B.
+     * - lineage A -> B merged with lineage B -> C returns lineage A -> B -> C
+     * - lineage A -> B with the {@code PERMISSION} capability revoked for A merged with
+     *  lineage A -> B with the {@code SHARED_USER_ID} capability revoked for A returns
+     *  lineage A -> B with both capabilities revoked for A.
+     * - lineage A -> B -> C merged with lineage A -> B -> D would return the original lineage
+     *  A -> B -> C since the current signer of both instances is not the same or in the
+     *   lineage of the other.
+     *
+     * @param otherSigningDetails The {@code SigningDetails} you would like to merge with.
+     * @return Merged {@code SigningDetails} instance when one has the same or an ancestor signer
+     *         of the other. If neither instance has a lineage, or if neither has the same or an
+     *         ancestor signer then this instance is returned.
+     */
+    public @NonNull SigningDetails mergeLineageWith(@NonNull SigningDetails otherSigningDetails) {
+        if (!hasPastSigningCertificates()) {
+            return otherSigningDetails.hasPastSigningCertificates()
+                    && otherSigningDetails.hasAncestorOrSelf(this) ? otherSigningDetails : this;
+        }
+        if (!otherSigningDetails.hasPastSigningCertificates()) {
+            return this;
+        }
+        // Use the utility method to determine which SigningDetails instance is the descendant
+        // and to confirm that the signing lineage does not diverge.
+        SigningDetails descendantSigningDetails = getDescendantOrSelf(otherSigningDetails);
+        if (descendantSigningDetails == null) {
+            return this;
+        }
+        return descendantSigningDetails == this ? mergeLineageWithAncestorOrSelf(
+                otherSigningDetails) : otherSigningDetails.mergeLineageWithAncestorOrSelf(this);
+    }
+
+    /**
+     * Merges the signing lineage of this instance with the lineage of the ancestor (or same)
+     * signer in the provided {@code otherSigningDetails}.
+     *
+     * @param otherSigningDetails The {@code SigningDetails} you would like to merge with.
+     * @return Merged {@code SigningDetails} instance.
+     */
+    private @NonNull SigningDetails mergeLineageWithAncestorOrSelf(
+            @NonNull SigningDetails otherSigningDetails) {
+        // This method should only be called with instances that contain lineages.
+        int index = mPastSigningCertificates.length - 1;
+        int otherIndex = otherSigningDetails.mPastSigningCertificates.length - 1;
+        if (index < 0 || otherIndex < 0) {
+            return this;
+        }
+
+        List<Signature> mergedSignatures = new ArrayList<>();
+        boolean capabilitiesModified = false;
+        // If this is a descendant lineage then add all of the descendant signer(s) to the
+        // merged lineage until the ancestor signer is reached.
+        while (index >= 0 && !mPastSigningCertificates[index].equals(
+                otherSigningDetails.mPastSigningCertificates[otherIndex])) {
+            mergedSignatures.add(new Signature(mPastSigningCertificates[index--]));
+        }
+        // If the signing lineage was exhausted then the provided ancestor is not actually an
+        // ancestor of this lineage.
+        if (index < 0) {
+            return this;
+        }
+
+        do {
+            // Add the common signer to the merged lineage with the most restrictive
+            // capabilities of the two lineages.
+            Signature signature = mPastSigningCertificates[index--];
+            Signature ancestorSignature =
+                    otherSigningDetails.mPastSigningCertificates[otherIndex--];
+            Signature mergedSignature = new Signature(signature);
+            int mergedCapabilities = signature.getFlags() & ancestorSignature.getFlags();
+            if (signature.getFlags() != mergedCapabilities) {
+                capabilitiesModified = true;
+                mergedSignature.setFlags(mergedCapabilities);
+            }
+            mergedSignatures.add(mergedSignature);
+        } while (index >= 0 && otherIndex >= 0 && mPastSigningCertificates[index].equals(
+                otherSigningDetails.mPastSigningCertificates[otherIndex]));
+
+        // If both lineages still have elements then their lineages have diverged; since this is
+        // not supported return the invoking instance.
+        if (index >= 0 && otherIndex >= 0) {
+            return this;
+        }
+
+        // Add any remaining elements from either lineage that is not yet exhausted to the
+        // the merged lineage.
+        while (otherIndex >= 0) {
+            mergedSignatures.add(new Signature(
+                    otherSigningDetails.mPastSigningCertificates[otherIndex--]));
+        }
+        while (index >= 0) {
+            mergedSignatures.add(new Signature(mPastSigningCertificates[index--]));
+        }
+
+        // if this lineage already contains all the elements in the ancestor and none of the
+        // capabilities were changed then just return this instance.
+        if (mergedSignatures.size() == mPastSigningCertificates.length
+                && !capabilitiesModified) {
+            return this;
+        }
+        // Since the signatures were added to the merged lineage from newest to oldest reverse
+        // the list to ensure the oldest signer is at index 0.
+        Collections.reverse(mergedSignatures);
+        try {
+            return new SigningDetails(new Signature[]{new Signature(mSignatures[0])},
+                    mSignatureSchemeVersion, mergedSignatures.toArray(new Signature[0]));
+        } catch (CertificateException e) {
+            Slog.e(TAG, "Caught an exception creating the merged lineage: ", e);
+            return this;
+        }
+    }
+
+    /**
+     * Returns whether this and the provided {@code otherSigningDetails} share a common
+     * ancestor.
+     *
+     * <p>The two SigningDetails have a common ancestor if any of the following conditions are
+     * met:
+     * - If neither has a lineage and their current signer(s) are equal.
+     * - If only one has a lineage and the signer of the other is the same or in the lineage.
+     * - If both have a lineage and their current signers are the same or one is in the lineage
+     * of the other, and their lineages do not diverge to different signers.
+     */
+    public boolean hasCommonAncestor(@NonNull SigningDetails otherSigningDetails) {
+        if (!hasPastSigningCertificates()) {
+            // If this instance does not have a lineage then it must either be in the ancestry
+            // of or the same signer of the otherSigningDetails.
+            return otherSigningDetails.hasAncestorOrSelf(this);
+        }
+        if (!otherSigningDetails.hasPastSigningCertificates()) {
+            return hasAncestorOrSelf(otherSigningDetails);
+        }
+        // If both have a lineage then use getDescendantOrSelf to obtain the descendant signing
+        // details; a null return from that method indicates there is no common lineage between
+        // the two or that they diverge at a point in the lineage.
+        return getDescendantOrSelf(otherSigningDetails) != null;
+    }
+
+    /**
+     * Returns whether this instance is currently signed, or has ever been signed, with a
+     * signing certificate from the provided {@link Set} of {@code certDigests}.
+     *
+     * <p>The provided {@code certDigests} should contain the SHA-256 digest of the DER encoding
+     * of each trusted certificate with the digest characters in upper case. If this instance
+     * has multiple signers then all signers must be in the provided {@code Set}. If this
+     * instance has a signing lineage then this method will return true if any of the previous
+     * signers in the lineage match one of the entries in the {@code Set}.
+     */
+    public boolean hasAncestorOrSelfWithDigest(@Nullable Set<String> certDigests) {
+        if (this == UNKNOWN || certDigests == null || certDigests.size() == 0) {
+            return false;
+        }
+        // If an app is signed by multiple signers then all of the signers must be in the Set.
+        if (mSignatures.length > 1) {
+            // If the Set has less elements than the number of signatures then immediately
+            // return false as there's no way to satisfy the requirement of all signatures being
+            // in the Set.
+            if (certDigests.size() < mSignatures.length) {
+                return false;
+            }
+            for (Signature signature : mSignatures) {
+                String signatureDigest = PackageUtils.computeSha256Digest(
+                        signature.toByteArray());
+                if (!certDigests.contains(signatureDigest)) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        String signatureDigest = PackageUtils.computeSha256Digest(mSignatures[0].toByteArray());
+        if (certDigests.contains(signatureDigest)) {
+            return true;
+        }
+        if (hasPastSigningCertificates()) {
+            // The last element in the pastSigningCertificates array is the current signer;
+            // since that was verified above just check all the signers in the lineage.
+            for (int i = 0; i < mPastSigningCertificates.length - 1; i++) {
+                signatureDigest = PackageUtils.computeSha256Digest(
+                        mPastSigningCertificates[i].toByteArray());
+                if (certDigests.contains(signatureDigest)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns the SigningDetails with a descendant (or same) signer after verifying the
+     * descendant has the same, a superset, or a subset of the lineage of the ancestor.
+     *
+     * <p>If this instance and the provided {@code otherSigningDetails} do not share an
+     * ancestry, or if their lineages diverge then null is returned to indicate there is no
+     * valid descendant SigningDetails.
+     */
+    private @Nullable SigningDetails getDescendantOrSelf(
+            @NonNull SigningDetails otherSigningDetails) {
+        final SigningDetails descendantSigningDetails;
+        final SigningDetails ancestorSigningDetails;
+        if (hasAncestorOrSelf(otherSigningDetails)) {
+            // If the otherSigningDetails has the same signer or a signer in the lineage of this
+            // instance then treat this instance as the descendant.
+            descendantSigningDetails = this;
+            ancestorSigningDetails = otherSigningDetails;
+        } else if (otherSigningDetails.hasAncestor(this)) {
+            // The above check confirmed that the two instances do not have the same signer and
+            // the signer of otherSigningDetails is not in this instance's lineage; if this
+            // signer is in the otherSigningDetails lineage then treat this as the ancestor.
+            descendantSigningDetails = otherSigningDetails;
+            ancestorSigningDetails = this;
+        } else {
+            // The signers are not the same and neither has the current signer of the other in
+            // its lineage; return null to indicate there is no descendant signer.
+            return null;
+        }
+        // Once the descent (or same) signer is identified iterate through the ancestry until
+        // the current signer of the ancestor is found.
+        int descendantIndex = descendantSigningDetails.mPastSigningCertificates.length - 1;
+        int ancestorIndex = ancestorSigningDetails.mPastSigningCertificates.length - 1;
+        while (descendantIndex >= 0
+                && !descendantSigningDetails.mPastSigningCertificates[descendantIndex].equals(
+                ancestorSigningDetails.mPastSigningCertificates[ancestorIndex])) {
+            descendantIndex--;
+        }
+        // Since the ancestry was verified above the descendant lineage should never be
+        // exhausted, but if for some reason the ancestor signer is not found then return null.
+        if (descendantIndex < 0) {
+            return null;
+        }
+        // Once the common ancestor (or same) signer is found iterate over the lineage of both
+        // to ensure that they are either the same or one is a subset of the other.
+        do {
+            descendantIndex--;
+            ancestorIndex--;
+        } while (descendantIndex >= 0 && ancestorIndex >= 0
+                && descendantSigningDetails.mPastSigningCertificates[descendantIndex].equals(
+                ancestorSigningDetails.mPastSigningCertificates[ancestorIndex]));
+
+        // If both lineages still have elements then they diverge and cannot be considered a
+        // valid common lineage.
+        if (descendantIndex >= 0 && ancestorIndex >= 0) {
+            return null;
+        }
+        // Since one or both of the lineages was exhausted they are either the same or one is a
+        // subset of the other; return the valid descendant.
+        return descendantSigningDetails;
+    }
+
+    /** Returns true if the signing details have one or more signatures. */
+    public boolean hasSignatures() {
+        return mSignatures != null && mSignatures.length > 0;
+    }
+
+    /** Returns true if the signing details have past signing certificates. */
+    public boolean hasPastSigningCertificates() {
+        return mPastSigningCertificates != null && mPastSigningCertificates.length > 0;
+    }
+
+    /**
+     * Determines if the provided {@code oldDetails} is an ancestor of or the same as this one.
+     * If the {@code oldDetails} signing certificate appears in our pastSigningCertificates,
+     * then that means it has authorized a signing certificate rotation, which eventually leads
+     * to our certificate, and thus can be trusted. If this method evaluates to true, this
+     * SigningDetails object should be trusted if the previous one is.
+     */
+    public boolean hasAncestorOrSelf(@NonNull SigningDetails oldDetails) {
+        if (this == UNKNOWN || oldDetails == UNKNOWN) {
+            return false;
+        }
+        if (oldDetails.mSignatures.length > 1) {
+            // multiple-signer packages cannot rotate signing certs, so we just compare current
+            // signers for an exact match
+            return signaturesMatchExactly(oldDetails);
+        } else {
+            // we may have signing certificate rotation history, check to see if the oldDetails
+            // was one of our old signing certificates
+            return hasCertificate(oldDetails.mSignatures[0]);
+        }
+    }
+
+    /**
+     * Similar to {@code hasAncestorOrSelf}. Returns true only if this {@code SigningDetails}
+     * is a descendant of {@code oldDetails}, not if they're the same. This is used to
+     * determine if this object is newer than the provided one.
+     */
+    public boolean hasAncestor(@NonNull SigningDetails oldDetails) {
+        if (this == UNKNOWN || oldDetails == UNKNOWN) {
+            return false;
+        }
+        if (hasPastSigningCertificates() && oldDetails.mSignatures.length == 1) {
+            // the last entry in pastSigningCertificates is the current signer, ignore it
+            for (int i = 0; i < mPastSigningCertificates.length - 1; i++) {
+                if (mPastSigningCertificates[i].equals(oldDetails.mSignatures[0])) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns whether this {@code SigningDetails} has a signer in common with the provided
+     * {@code otherDetails} with the specified {@code flags} capabilities provided by this
+     * signer.
+     *
+     * <p>Note this method allows for the signing lineage to diverge, so this should only be
+     * used for instances where the only requirement is a common signer in the lineage with
+     * the specified capabilities. If the current signer of this instance is an ancestor of
+     * {@code otherDetails} then {@code true} is immediately returned since the current signer
+     * has all capabilities granted.
+     */
+    public boolean hasCommonSignerWithCapability(@NonNull SigningDetails otherDetails,
+            @CertCapabilities int flags) {
+        if (this == UNKNOWN || otherDetails == UNKNOWN) {
+            return false;
+        }
+        // If either is signed with more than one signer then both must be signed by the same
+        // signers to consider the capabilities granted.
+        if (mSignatures.length > 1 || otherDetails.mSignatures.length > 1) {
+            return signaturesMatchExactly(otherDetails);
+        }
+        // The Signature class does not use the granted capabilities in the hashCode
+        // computation, so a Set can be used to check for a common signer.
+        Set<Signature> otherSignatures = new ArraySet<>();
+        if (otherDetails.hasPastSigningCertificates()) {
+            otherSignatures.addAll(Arrays.asList(otherDetails.mPastSigningCertificates));
+        } else {
+            otherSignatures.addAll(Arrays.asList(otherDetails.mSignatures));
+        }
+        // If the current signer of this instance is an ancestor of the other than return true
+        // since all capabilities are granted to the current signer.
+        if (otherSignatures.contains(mSignatures[0])) {
+            return true;
+        }
+        if (hasPastSigningCertificates()) {
+            // Since the current signer was checked above and the last signature in the
+            // pastSigningCertificates is the current signer skip checking the last element.
+            for (int i = 0; i < mPastSigningCertificates.length - 1; i++) {
+                if (otherSignatures.contains(mPastSigningCertificates[i])) {
+                    // If the caller specified multiple capabilities ensure all are set.
+                    if ((mPastSigningCertificates[i].getFlags() & flags) == flags) {
+                        return true;
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Determines if the provided {@code oldDetails} is an ancestor of this one, and whether or
+     * not this one grants it the provided capability, represented by the {@code flags}
+     * parameter.  In the event of signing certificate rotation, a package may still interact
+     * with entities signed by its old signing certificate and not want to break previously
+     * functioning behavior.  The {@code flags} value determines which capabilities the app
+     * signed by the newer signing certificate would like to continue to give to its previous
+     * signing certificate(s).
+     */
+    public boolean checkCapability(@NonNull SigningDetails oldDetails,
+            @CertCapabilities int flags) {
+        if (this == UNKNOWN || oldDetails == UNKNOWN) {
+            return false;
+        }
+        if (oldDetails.mSignatures.length > 1) {
+            // multiple-signer packages cannot rotate signing certs, so we must have an exact
+            // match, which also means all capabilities are granted
+            return signaturesMatchExactly(oldDetails);
+        } else {
+            // we may have signing certificate rotation history, check to see if the oldDetails
+            // was one of our old signing certificates, and if we grant it the capability it's
+            // requesting
+            return hasCertificate(oldDetails.mSignatures[0], flags);
+        }
+    }
+
+    /**
+     * A special case of {@code checkCapability} which re-encodes both sets of signing
+     * certificates to counteract a previous re-encoding.
+     */
+    public boolean checkCapabilityRecover(@NonNull SigningDetails oldDetails,
+            @CertCapabilities int flags) throws CertificateException {
+        if (oldDetails == UNKNOWN || this == UNKNOWN) {
+            return false;
+        }
+        if (hasPastSigningCertificates() && oldDetails.mSignatures.length == 1) {
+            // signing certificates may have rotated, check entire history for effective match
+            for (int i = 0; i < mPastSigningCertificates.length; i++) {
+                if (Signature.areEffectiveMatch(
+                        oldDetails.mSignatures[0],
+                        mPastSigningCertificates[i])
+                        && mPastSigningCertificates[i].getFlags() == flags) {
+                    return true;
+                }
+            }
+        } else {
+            return Signature.areEffectiveMatch(oldDetails.mSignatures, mSignatures);
+        }
+        return false;
+    }
+
+    /**
+     * Determine if {@code signature} is in this SigningDetails' signing certificate history,
+     * including the current signer.  Automatically returns false if this object has multiple
+     * signing certificates, since rotation is only supported for single-signers; this is
+     * enforced by {@code hasCertificateInternal}.
+     */
+    public boolean hasCertificate(@NonNull Signature signature) {
+        return hasCertificateInternal(signature, PAST_CERT_EXISTS);
+    }
+
+    /**
+     * Determine if {@code signature} is in this SigningDetails' signing certificate history,
+     * including the current signer, and whether or not it has the given permission.
+     * Certificates which match our current signer automatically get all capabilities.
+     * Automatically returns false if this object has multiple signing certificates, since
+     * rotation is only supported for single-signers.
+     */
+    public boolean hasCertificate(@NonNull Signature signature, @CertCapabilities int flags) {
+        return hasCertificateInternal(signature, flags);
+    }
+
+    /** Convenient wrapper for calling {@code hasCertificate} with certificate's raw bytes. */
+    public boolean hasCertificate(byte[] certificate) {
+        Signature signature = new Signature(certificate);
+        return hasCertificate(signature);
+    }
+
+    private boolean hasCertificateInternal(@NonNull Signature signature, int flags) {
+        if (this == UNKNOWN) {
+            return false;
+        }
+
+        // only single-signed apps can have pastSigningCertificates
+        if (hasPastSigningCertificates()) {
+            // check all past certs, except for the current one, which automatically gets all
+            // capabilities, since it is the same as the current signature
+            for (int i = 0; i < mPastSigningCertificates.length - 1; i++) {
+                if (mPastSigningCertificates[i].equals(signature)) {
+                    if (flags == PAST_CERT_EXISTS
+                            || (flags & mPastSigningCertificates[i].getFlags()) == flags) {
+                        return true;
+                    }
+                }
+            }
+        }
+
+        // not in previous certs signing history, just check the current signer and make sure
+        // we are singly-signed
+        return mSignatures.length == 1 && mSignatures[0].equals(signature);
+    }
+
+    /**
+     * Determines if the provided {@code sha256String} is an ancestor of this one, and whether
+     * or not this one grants it the provided capability, represented by the {@code flags}
+     * parameter.  In the event of signing certificate rotation, a package may still interact
+     * with entities signed by its old signing certificate and not want to break previously
+     * functioning behavior.  The {@code flags} value determines which capabilities the app
+     * signed by the newer signing certificate would like to continue to give to its previous
+     * signing certificate(s).
+     *
+     * @param sha256String A hex-encoded representation of a sha256 digest. In the case of an
+     *                     app with multiple signers, this represents the hex-encoded sha256
+     *                     digest of the combined hex-encoded sha256 digests of each individual
+     *                     signing certificate according to {@link
+     *                     PackageUtils#computeSignaturesSha256Digest(Signature[])}
+     */
+    public boolean checkCapability(@Nullable String sha256String, @CertCapabilities int flags) {
+        if (this == UNKNOWN || TextUtils.isEmpty(sha256String)) {
+            return false;
+        }
+
+        // first see if the hash represents a single-signer in our signing history
+        final byte[] sha256Bytes = HexEncoding.decode(sha256String, false /* allowSingleChar */);
+        if (hasSha256Certificate(sha256Bytes, flags)) {
+            return true;
+        }
+
+        // Not in signing history, either represents multiple signatures or not a match.
+        // Multiple signers can't rotate, so no need to check flags, just see if the SHAs match.
+        // We already check the single-signer case above as part of hasSha256Certificate, so no
+        // need to verify we have multiple signers, just run the old check
+        // just consider current signing certs
+        final String[] mSignaturesSha256Digests =
+                PackageUtils.computeSignaturesSha256Digests(mSignatures);
+        final String mSignaturesSha256Digest =
+                PackageUtils.computeSignaturesSha256Digest(mSignaturesSha256Digests);
+        return mSignaturesSha256Digest.equals(sha256String);
+    }
+
+    /**
+     * Determine if the {@code sha256Certificate} is in this SigningDetails' signing certificate
+     * history, including the current signer.  Automatically returns false if this object has
+     * multiple signing certificates, since rotation is only supported for single-signers.
+     */
+    public boolean hasSha256Certificate(byte[] sha256Certificate) {
+        return hasSha256CertificateInternal(sha256Certificate, PAST_CERT_EXISTS);
+    }
+
+    /**
+     * Determine if the {@code sha256Certificate} certificate hash corresponds to a signing
+     * certificate in this SigningDetails' signing certificate history, including the current
+     * signer, and whether or not it has the given permission.  Certificates which match our
+     * current signer automatically get all capabilities. Automatically returns false if this
+     * object has multiple signing certificates, since rotation is only supported for
+     * single-signers.
+     */
+    public boolean hasSha256Certificate(byte[] sha256Certificate, @CertCapabilities int flags) {
+        return hasSha256CertificateInternal(sha256Certificate, flags);
+    }
+
+    private boolean hasSha256CertificateInternal(byte[] sha256Certificate, int flags) {
+        if (this == UNKNOWN) {
+            return false;
+        }
+        if (hasPastSigningCertificates()) {
+            // check all past certs, except for the last one, which automatically gets all
+            // capabilities, since it is the same as the current signature, and is checked below
+            for (int i = 0; i < mPastSigningCertificates.length - 1; i++) {
+                byte[] digest = PackageUtils.computeSha256DigestBytes(
+                        mPastSigningCertificates[i].toByteArray());
+                if (Arrays.equals(sha256Certificate, digest)) {
+                    if (flags == PAST_CERT_EXISTS
+                            || (flags & mPastSigningCertificates[i].getFlags()) == flags) {
+                        return true;
+                    }
+                }
+            }
+        }
+
+        // not in previous certs signing history, just check the current signer
+        if (mSignatures.length == 1) {
+            byte[] digest = PackageUtils.computeSha256DigestBytes(mSignatures[0].toByteArray());
+            return Arrays.equals(sha256Certificate, digest);
+        }
+        return false;
+    }
+
+    /** Returns true if the signatures in this and other match exactly. */
+    public boolean signaturesMatchExactly(@NonNull SigningDetails other) {
+        return Signature.areExactMatch(mSignatures, other.mSignatures);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        boolean isUnknown = UNKNOWN == this;
+        dest.writeBoolean(isUnknown);
+        if (isUnknown) {
+            return;
+        }
+        dest.writeTypedArray(mSignatures, flags);
+        dest.writeInt(mSignatureSchemeVersion);
+        dest.writeArraySet(mPublicKeys);
+        dest.writeTypedArray(mPastSigningCertificates, flags);
+    }
+
+    protected SigningDetails(@NonNull Parcel in) {
+        final ClassLoader boot = Object.class.getClassLoader();
+        mSignatures = in.createTypedArray(Signature.CREATOR);
+        mSignatureSchemeVersion = in.readInt();
+        mPublicKeys = (ArraySet<PublicKey>) in.readArraySet(boot);
+        mPastSigningCertificates = in.createTypedArray(Signature.CREATOR);
+    }
+
+    public static final @NonNull Parcelable.Creator<SigningDetails> CREATOR =
+            new Creator<SigningDetails>() {
+                @Override
+                public SigningDetails createFromParcel(@NonNull Parcel source) {
+                    if (source.readBoolean()) {
+                        return UNKNOWN;
+                    }
+                    return new SigningDetails(source);
+                }
+
+                @Override
+                public SigningDetails[] newArray(int size) {
+                    return new SigningDetails[size];
+                }
+            };
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (this == o) return true;
+        if (!(o instanceof SigningDetails)) return false;
+
+        final SigningDetails that = (SigningDetails) o;
+
+        if (mSignatureSchemeVersion != that.mSignatureSchemeVersion) return false;
+        if (!Signature.areExactMatch(mSignatures, that.mSignatures)) return false;
+        if (mPublicKeys != null) {
+            if (!mPublicKeys.equals((that.mPublicKeys))) {
+                return false;
+            }
+        } else if (that.mPublicKeys != null) {
+            return false;
+        }
+
+        // can't use Signature.areExactMatch() because order matters with the past signing certs
+        if (!Arrays.equals(mPastSigningCertificates, that.mPastSigningCertificates)) {
+            return false;
+        }
+        // The capabilities for the past signing certs must match as well.
+        for (int i = 0; i < mPastSigningCertificates.length; i++) {
+            if (mPastSigningCertificates[i].getFlags()
+                    != that.mPastSigningCertificates[i].getFlags()) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = +Arrays.hashCode(mSignatures);
+        result = 31 * result + mSignatureSchemeVersion;
+        result = 31 * result + (mPublicKeys != null ? mPublicKeys.hashCode() : 0);
+        result = 31 * result + Arrays.hashCode(mPastSigningCertificates);
+        return result;
+    }
+
+    /**
+     * Builder of {@code SigningDetails} instances.
+     */
+    public static class Builder {
+        private @NonNull Signature[] mSignatures;
+        private @SignatureSchemeVersion int mSignatureSchemeVersion =
+                SignatureSchemeVersion.UNKNOWN;
+        private @Nullable Signature[] mPastSigningCertificates;
+
+        public Builder() {
+        }
+
+        /** get signing certificates used to sign the current APK */
+        public SigningDetails.Builder setSignatures(@NonNull Signature[] signatures) {
+            mSignatures = signatures;
+            return this;
+        }
+
+        /** set the signature scheme version used to sign the APK */
+        public SigningDetails.Builder setSignatureSchemeVersion(
+                @SignatureSchemeVersion int signatureSchemeVersion) {
+            mSignatureSchemeVersion = signatureSchemeVersion;
+            return this;
+        }
+
+        /** set the signing certificates by which the APK proved it can be authenticated */
+        public SigningDetails.Builder setPastSigningCertificates(
+                @Nullable Signature[] pastSigningCertificates) {
+            mPastSigningCertificates = pastSigningCertificates;
+            return this;
+        }
+
+        private void checkInvariants() {
+            // must have signatures and scheme version set
+            if (mSignatures == null) {
+                throw new IllegalStateException("SigningDetails requires the current signing"
+                        + " certificates.");
+            }
+        }
+        /** build a {@code SigningDetails} object */
+        public SigningDetails build()
+                throws CertificateException {
+            checkInvariants();
+            return new SigningDetails(mSignatures, mSignatureSchemeVersion,
+                    mPastSigningCertificates);
+        }
+    }
+
+    /** Parses the public keys from the set of signatures. */
+    public static ArraySet<PublicKey> toSigningKeys(@NonNull Signature[] signatures)
+            throws CertificateException {
+        final ArraySet<PublicKey> keys = new ArraySet<>(signatures.length);
+        for (int i = 0; i < signatures.length; i++) {
+            keys.add(signatures[i].getPublicKey());
+        }
+        return keys;
+    }
+
+
+
+    // Code below generated by codegen v1.0.22.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/SigningDetails.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    /**
+     * The signing certificates associated with this application package.
+     */
+    @DataClass.Generated.Member
+    public @Nullable Signature[] getSignatures() {
+        return mSignatures;
+    }
+
+    /**
+     * The signature scheme version for this application package.
+     */
+    @DataClass.Generated.Member
+    public @SignatureSchemeVersion int getSignatureSchemeVersion() {
+        return mSignatureSchemeVersion;
+    }
+
+    /**
+     * The public keys set for the certificates.
+     */
+    @DataClass.Generated.Member
+    public @Nullable ArraySet<PublicKey> getPublicKeys() {
+        return mPublicKeys;
+    }
+
+    /**
+     * APK Signature Scheme v3 includes support for adding a proof-of-rotation record that
+     * contains two pieces of information:
+     *   1) the past signing certificates
+     *   2) the flags that APK wants to assign to each of the past signing certificates.
+     *
+     * This collection of {@code Signature} objects, each of which is formed from a former
+     * signing certificate of this APK before it was changed by signing certificate rotation,
+     * represents the first piece of information.  It is the APK saying to the rest of the
+     * world: "hey if you trust the old cert, you can trust me!"  This is useful, if for
+     * instance, the platform would like to determine whether or not to allow this APK to do
+     * something it would've allowed it to do under the old cert (like upgrade).
+     */
+    @DataClass.Generated.Member
+    public @Nullable Signature[] getPastSigningCertificates() {
+        return mPastSigningCertificates;
+    }
+
+    @DataClass.Generated(
+            time = 1616984092921L,
+            codegenVersion = "1.0.22",
+            sourceFile = "frameworks/base/core/java/android/content/pm/SigningDetails.java",
+            inputSignatures = "private static final  java.lang.String TAG\nprivate final @android.annotation.Nullable android.content.pm.Signature[] mSignatures\nprivate final @android.content.pm.SigningDetails.SignatureSchemeVersion int mSignatureSchemeVersion\nprivate final @android.annotation.Nullable android.util.ArraySet<java.security.PublicKey> mPublicKeys\nprivate final @android.annotation.Nullable android.content.pm.Signature[] mPastSigningCertificates\nprivate static final  int PAST_CERT_EXISTS\npublic static final  android.content.pm.SigningDetails UNKNOWN\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<android.content.pm.SigningDetails> CREATOR\npublic @android.annotation.NonNull android.content.pm.SigningDetails mergeLineageWith(android.content.pm.SigningDetails)\nprivate @android.annotation.NonNull android.content.pm.SigningDetails mergeLineageWithAncestorOrSelf(android.content.pm.SigningDetails)\npublic  boolean hasCommonAncestor(android.content.pm.SigningDetails)\npublic  boolean hasAncestorOrSelfWithDigest(java.util.Set<java.lang.String>)\nprivate @android.annotation.Nullable android.content.pm.SigningDetails getDescendantOrSelf(android.content.pm.SigningDetails)\npublic  boolean hasSignatures()\npublic  boolean hasPastSigningCertificates()\npublic  boolean hasAncestorOrSelf(android.content.pm.SigningDetails)\npublic  boolean hasAncestor(android.content.pm.SigningDetails)\npublic  boolean hasCommonSignerWithCapability(android.content.pm.SigningDetails,int)\npublic  boolean checkCapability(android.content.pm.SigningDetails,int)\npublic  boolean checkCapabilityRecover(android.content.pm.SigningDetails,int)\npublic  boolean hasCertificate(android.content.pm.Signature)\npublic  boolean hasCertificate(android.content.pm.Signature,int)\npublic  boolean hasCertificate(byte[])\nprivate  boolean hasCertificateInternal(android.content.pm.Signature,int)\npublic  boolean checkCapability(java.lang.String,int)\npublic  boolean hasSha256Certificate(byte[])\npublic  boolean hasSha256Certificate(byte[],int)\nprivate  boolean hasSha256CertificateInternal(byte[],int)\npublic  boolean signaturesMatchExactly(android.content.pm.SigningDetails)\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\npublic @java.lang.Override boolean equals(java.lang.Object)\npublic @java.lang.Override int hashCode()\npublic static  android.util.ArraySet<java.security.PublicKey> toSigningKeys(android.content.pm.Signature[])\nclass SigningDetails extends java.lang.Object implements [android.os.Parcelable]\nprivate @android.annotation.NonNull android.content.pm.Signature[] mSignatures\nprivate @android.content.pm.SigningDetails.SignatureSchemeVersion int mSignatureSchemeVersion\nprivate @android.annotation.Nullable android.content.pm.Signature[] mPastSigningCertificates\npublic  android.content.pm.SigningDetails.Builder setSignatures(android.content.pm.Signature[])\npublic  android.content.pm.SigningDetails.Builder setSignatureSchemeVersion(int)\npublic  android.content.pm.SigningDetails.Builder setPastSigningCertificates(android.content.pm.Signature[])\nprivate  void checkInvariants()\npublic  android.content.pm.SigningDetails build()\nclass Builder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false, genParcelable=true, genAidl=false)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/core/java/android/content/pm/SigningInfo.java b/core/java/android/content/pm/SigningInfo.java
index d14be9c..7459a90 100644
--- a/core/java/android/content/pm/SigningInfo.java
+++ b/core/java/android/content/pm/SigningInfo.java
@@ -16,7 +16,6 @@
 
 package android.content.pm;
 
-
 import android.annotation.NonNull;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -27,25 +26,25 @@
 public final class SigningInfo implements Parcelable {
 
     @NonNull
-    private final PackageParser.SigningDetails mSigningDetails;
+    private final SigningDetails mSigningDetails;
 
     public SigningInfo() {
-        mSigningDetails = PackageParser.SigningDetails.UNKNOWN;
+        mSigningDetails = SigningDetails.UNKNOWN;
     }
 
     /**
      * @hide only packagemanager should be populating this
      */
-    public SigningInfo(PackageParser.SigningDetails signingDetails) {
-        mSigningDetails = new PackageParser.SigningDetails(signingDetails);
+    public SigningInfo(SigningDetails signingDetails) {
+        mSigningDetails = new SigningDetails(signingDetails);
     }
 
     public SigningInfo(SigningInfo orig) {
-        mSigningDetails = new PackageParser.SigningDetails(orig.mSigningDetails);
+        mSigningDetails = new SigningDetails(orig.mSigningDetails);
     }
 
     private SigningInfo(Parcel source) {
-        mSigningDetails = PackageParser.SigningDetails.CREATOR.createFromParcel(source);
+        mSigningDetails = SigningDetails.CREATOR.createFromParcel(source);
     }
 
     /**
@@ -53,7 +52,8 @@
      * their identity is viewed as being the set of all signers, not just any one.
      */
     public boolean hasMultipleSigners() {
-        return mSigningDetails.signatures != null && mSigningDetails.signatures.length > 1;
+        return mSigningDetails.getSignatures() != null
+                && mSigningDetails.getSignatures().length > 1;
     }
 
     /**
@@ -65,8 +65,8 @@
      * signing history, since it could change to a new signing certificate at any time.
      */
     public boolean hasPastSigningCertificates() {
-        return mSigningDetails.signatures != null
-                && mSigningDetails.pastSigningCertificates != null;
+        return mSigningDetails.getPastSigningCertificates() != null
+                && mSigningDetails.getPastSigningCertificates().length > 0;
     }
 
     /**
@@ -93,11 +93,11 @@
         } else if (!hasPastSigningCertificates()) {
 
             // this package is only signed by one signer with no history, return it
-            return mSigningDetails.signatures;
+            return mSigningDetails.getSignatures();
         } else {
 
             // this package has provided proof of past signing certificates, include them
-            return mSigningDetails.pastSigningCertificates;
+            return mSigningDetails.getPastSigningCertificates();
         }
     }
 
@@ -111,7 +111,7 @@
      * </note>
      */
     public Signature[] getApkContentsSigners() {
-        return mSigningDetails.signatures;
+        return mSigningDetails.getSignatures();
     }
 
     @Override
diff --git a/core/java/android/content/pm/parsing/ApkLite.java b/core/java/android/content/pm/parsing/ApkLite.java
index d8ec512..024c18c 100644
--- a/core/java/android/content/pm/parsing/ApkLite.java
+++ b/core/java/android/content/pm/parsing/ApkLite.java
@@ -19,7 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.pm.PackageInfo;
-import android.content.pm.PackageParser.SigningDetails;
+import android.content.pm.SigningDetails;
 import android.content.pm.VerifierInfo;
 
 import com.android.internal.util.DataClass;
@@ -398,10 +398,10 @@
     }
 
     @DataClass.Generated(
-            time = 1610596637723L,
+            time = 1616985847981L,
             codegenVersion = "1.0.22",
             sourceFile = "frameworks/base/core/java/android/content/pm/parsing/ApkLite.java",
-            inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.Nullable java.lang.String mSplitName\nprivate final @android.annotation.Nullable java.lang.String mUsesSplitName\nprivate final @android.annotation.Nullable java.lang.String mConfigForSplit\nprivate final  int mVersionCodeMajor\nprivate final  int mVersionCode\nprivate final  int mRevisionCode\nprivate final  int mInstallLocation\nprivate final  int mMinSdkVersion\nprivate final  int mTargetSdkVersion\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.NonNull android.content.pm.PackageParser.SigningDetails mSigningDetails\nprivate final  boolean mFeatureSplit\nprivate final  boolean mIsolatedSplits\nprivate final  boolean mSplitRequired\nprivate final  boolean mCoreApp\nprivate final  boolean mDebuggable\nprivate final  boolean mProfileableByShell\nprivate final  boolean mMultiArch\nprivate final  boolean mUse32bitAbi\nprivate final  boolean mExtractNativeLibs\nprivate final  boolean mUseEmbeddedDex\nprivate final @android.annotation.Nullable java.lang.String mTargetPackageName\nprivate final  boolean mOverlayIsStatic\nprivate final  int mOverlayPriority\nprivate final  int mRollbackDataPolicy\npublic  long getLongVersionCode()\nclass ApkLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)")
+            inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.Nullable java.lang.String mSplitName\nprivate final @android.annotation.Nullable java.lang.String mUsesSplitName\nprivate final @android.annotation.Nullable java.lang.String mConfigForSplit\nprivate final  int mVersionCodeMajor\nprivate final  int mVersionCode\nprivate final  int mRevisionCode\nprivate final  int mInstallLocation\nprivate final  int mMinSdkVersion\nprivate final  int mTargetSdkVersion\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.NonNull android.content.pm.SigningDetails mSigningDetails\nprivate final  boolean mFeatureSplit\nprivate final  boolean mIsolatedSplits\nprivate final  boolean mSplitRequired\nprivate final  boolean mCoreApp\nprivate final  boolean mDebuggable\nprivate final  boolean mProfileableByShell\nprivate final  boolean mMultiArch\nprivate final  boolean mUse32bitAbi\nprivate final  boolean mExtractNativeLibs\nprivate final  boolean mUseEmbeddedDex\nprivate final @android.annotation.Nullable java.lang.String mTargetPackageName\nprivate final  boolean mOverlayIsStatic\nprivate final  int mOverlayPriority\nprivate final  int mRollbackDataPolicy\npublic  long getLongVersionCode()\nclass ApkLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
index 5887047..7805e8d 100644
--- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
@@ -25,6 +25,7 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageParser;
+import android.content.pm.SigningDetails;
 import android.content.pm.VerifierInfo;
 import android.content.pm.parsing.result.ParseInput;
 import android.content.pm.parsing.result.ParseResult;
@@ -302,16 +303,15 @@
 
             parser = apkAssets.openXml(ParsingPackageUtils.ANDROID_MANIFEST_FILENAME);
 
-            final PackageParser.SigningDetails signingDetails;
+            final SigningDetails signingDetails;
             if ((flags & ParsingPackageUtils.PARSE_COLLECT_CERTIFICATES) != 0) {
                 final boolean skipVerify = (flags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) != 0;
                 Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "collectCertificates");
                 try {
-                    ParseResult<PackageParser.SigningDetails> result =
-                            ParsingPackageUtils.getSigningDetails(input,
-                                    apkFile.getAbsolutePath(), skipVerify, false,
-                                    PackageParser.SigningDetails.UNKNOWN,
-                                    DEFAULT_TARGET_SDK_VERSION);
+                    final ParseResult<SigningDetails> result =
+                            ParsingPackageUtils.getSigningDetails(input, apkFile.getAbsolutePath(),
+                                    skipVerify, /* isStaticSharedLibrary */ false,
+                                    SigningDetails.UNKNOWN, DEFAULT_TARGET_SDK_VERSION);
                     if (result.isError()) {
                         return input.error(result);
                     }
@@ -320,7 +320,7 @@
                     Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
                 }
             } else {
-                signingDetails = PackageParser.SigningDetails.UNKNOWN;
+                signingDetails = SigningDetails.UNKNOWN;
             }
 
             final AttributeSet attrs = parser;
@@ -342,7 +342,7 @@
     }
 
     private static ParseResult<ApkLite> parseApkLite(ParseInput input, String codePath,
-            XmlPullParser parser, AttributeSet attrs, PackageParser.SigningDetails signingDetails)
+            XmlPullParser parser, AttributeSet attrs, SigningDetails signingDetails)
             throws IOException, XmlPullParserException {
         ParseResult<Pair<String, String>> result = parsePackageSplitNames(input, parser, attrs);
         if (result.isError()) {
diff --git a/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java b/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java
index e0052da..f4bc250 100644
--- a/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java
+++ b/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java
@@ -32,7 +32,6 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageItemInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageParser;
 import android.content.pm.PackageUserState;
 import android.content.pm.PermissionGroupInfo;
 import android.content.pm.PermissionInfo;
@@ -40,6 +39,7 @@
 import android.content.pm.SELinuxUtil;
 import android.content.pm.ServiceInfo;
 import android.content.pm.Signature;
+import android.content.pm.SigningDetails;
 import android.content.pm.SigningInfo;
 import android.content.pm.overlay.OverlayPaths;
 import android.content.pm.parsing.component.ComponentParseUtils;
@@ -320,26 +320,26 @@
             pi.isApex = true;
         }
 
-        PackageParser.SigningDetails signingDetails = pkg.getSigningDetails();
+        final SigningDetails signingDetails = pkg.getSigningDetails();
         // deprecated method of getting signing certificates
         if ((flags & PackageManager.GET_SIGNATURES) != 0) {
             if (signingDetails.hasPastSigningCertificates()) {
                 // Package has included signing certificate rotation information.  Return the oldest
                 // cert so that programmatic checks keep working even if unaware of key rotation.
                 pi.signatures = new Signature[1];
-                pi.signatures[0] = signingDetails.pastSigningCertificates[0];
+                pi.signatures[0] = signingDetails.getPastSigningCertificates()[0];
             } else if (signingDetails.hasSignatures()) {
                 // otherwise keep old behavior
-                int numberOfSigs = signingDetails.signatures.length;
+                int numberOfSigs = signingDetails.getSignatures().length;
                 pi.signatures = new Signature[numberOfSigs];
-                System.arraycopy(signingDetails.signatures, 0, pi.signatures, 0,
+                System.arraycopy(signingDetails.getSignatures(), 0, pi.signatures, 0,
                         numberOfSigs);
             }
         }
 
         // replacement for GET_SIGNATURES
         if ((flags & PackageManager.GET_SIGNING_CERTIFICATES) != 0) {
-            if (signingDetails != PackageParser.SigningDetails.UNKNOWN) {
+            if (signingDetails != SigningDetails.UNKNOWN) {
                 // only return a valid SigningInfo if there is signing information to report
                 pi.signingInfo = new SigningInfo(signingDetails);
             } else {
diff --git a/core/java/android/content/pm/parsing/ParsingPackage.java b/core/java/android/content/pm/parsing/ParsingPackage.java
index 8dcba7f..5ecfd3c 100644
--- a/core/java/android/content/pm/parsing/ParsingPackage.java
+++ b/core/java/android/content/pm/parsing/ParsingPackage.java
@@ -25,7 +25,7 @@
 import android.content.pm.FeatureGroupInfo;
 import android.content.pm.FeatureInfo;
 import android.content.pm.PackageManager.Property;
-import android.content.pm.PackageParser;
+import android.content.pm.SigningDetails;
 import android.content.pm.parsing.component.ParsedActivity;
 import android.content.pm.parsing.component.ParsedAttribution;
 import android.content.pm.parsing.component.ParsedInstrumentation;
@@ -318,7 +318,7 @@
 
     ParsingPackage setSharedUserLabel(int sharedUserLabel);
 
-    ParsingPackage setSigningDetails(PackageParser.SigningDetails signingDetails);
+    ParsingPackage setSigningDetails(SigningDetails signingDetails);
 
     ParsingPackage setSplitClassLoaderName(int splitIndex, String classLoaderName);
 
diff --git a/core/java/android/content/pm/parsing/ParsingPackageImpl.java b/core/java/android/content/pm/parsing/ParsingPackageImpl.java
index ea7135e..76c1f18 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageImpl.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageImpl.java
@@ -32,7 +32,7 @@
 import android.content.pm.FeatureInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager.Property;
-import android.content.pm.PackageParser;
+import android.content.pm.SigningDetails;
 import android.content.pm.parsing.component.ParsedActivity;
 import android.content.pm.parsing.component.ParsedAttribution;
 import android.content.pm.parsing.component.ParsedComponent;
@@ -292,7 +292,7 @@
     @DataClass.ParcelWith(ForInternedString.class)
     protected String volumeUuid;
     @Nullable
-    private PackageParser.SigningDetails signingDetails;
+    private SigningDetails signingDetails;
 
     @NonNull
     @DataClass.ParcelWith(ForInternedString.class)
@@ -1708,7 +1708,7 @@
 
     @Nullable
     @Override
-    public PackageParser.SigningDetails getSigningDetails() {
+    public SigningDetails getSigningDetails() {
         return signingDetails;
     }
 
@@ -2255,7 +2255,7 @@
     }
 
     @Override
-    public ParsingPackageImpl setSigningDetails(@Nullable PackageParser.SigningDetails value) {
+    public ParsingPackageImpl setSigningDetails(@Nullable SigningDetails value) {
         signingDetails = value;
         return this;
     }
diff --git a/core/java/android/content/pm/parsing/ParsingPackageRead.java b/core/java/android/content/pm/parsing/ParsingPackageRead.java
index 4d4cc1a..9e43f83 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageRead.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageRead.java
@@ -26,8 +26,8 @@
 import android.content.pm.FeatureInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager.Property;
-import android.content.pm.PackageParser;
 import android.content.pm.ServiceInfo;
+import android.content.pm.SigningDetails;
 import android.content.pm.parsing.component.ParsedActivity;
 import android.content.pm.parsing.component.ParsedAttribution;
 import android.content.pm.parsing.component.ParsedInstrumentation;
@@ -766,7 +766,7 @@
      * The signature data of all APKs in this package, which must be exactly the same across the
      * base and splits.
      */
-    PackageParser.SigningDetails getSigningDetails();
+    SigningDetails getSigningDetails();
 
     /**
      * @see ApplicationInfo#splitClassLoaderNames
diff --git a/core/java/android/content/pm/parsing/ParsingPackageUtils.java b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
index a1ffc0c..f3c71ce 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageUtils.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
@@ -47,8 +47,8 @@
 import android.content.pm.PackageManager.Property;
 import android.content.pm.PackageParser;
 import android.content.pm.PackageParser.PackageParserException;
-import android.content.pm.PackageParser.SigningDetails;
 import android.content.pm.Signature;
+import android.content.pm.SigningDetails;
 import android.content.pm.parsing.component.ComponentParseUtils;
 import android.content.pm.parsing.component.ParsedActivity;
 import android.content.pm.parsing.component.ParsedActivityUtils;
@@ -3038,7 +3038,8 @@
         if (existingSigningDetails == SigningDetails.UNKNOWN) {
             return input.success(verified);
         } else {
-            if (!Signature.areExactMatch(existingSigningDetails.signatures, verified.signatures)) {
+            if (!Signature.areExactMatch(existingSigningDetails.getSignatures(),
+                    verified.getSignatures())) {
                 return input.error(INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES,
                         baseCodePath + " has mismatched certificates");
             }
diff --git a/core/java/android/hardware/soundtrigger/ConversionUtil.java b/core/java/android/hardware/soundtrigger/ConversionUtil.java
index a5c9a7f..ad0dc09 100644
--- a/core/java/android/hardware/soundtrigger/ConversionUtil.java
+++ b/core/java/android/hardware/soundtrigger/ConversionUtil.java
@@ -19,19 +19,19 @@
 import android.annotation.Nullable;
 import android.media.AudioFormat;
 import android.media.audio.common.AudioConfig;
-import android.media.soundtrigger_middleware.AudioCapabilities;
-import android.media.soundtrigger_middleware.ConfidenceLevel;
-import android.media.soundtrigger_middleware.ModelParameterRange;
-import android.media.soundtrigger_middleware.Phrase;
-import android.media.soundtrigger_middleware.PhraseRecognitionEvent;
-import android.media.soundtrigger_middleware.PhraseRecognitionExtra;
-import android.media.soundtrigger_middleware.PhraseSoundModel;
-import android.media.soundtrigger_middleware.RecognitionConfig;
-import android.media.soundtrigger_middleware.RecognitionEvent;
-import android.media.soundtrigger_middleware.RecognitionMode;
-import android.media.soundtrigger_middleware.SoundModel;
+import android.media.soundtrigger.AudioCapabilities;
+import android.media.soundtrigger.ConfidenceLevel;
+import android.media.soundtrigger.ModelParameterRange;
+import android.media.soundtrigger.Phrase;
+import android.media.soundtrigger.PhraseRecognitionEvent;
+import android.media.soundtrigger.PhraseRecognitionExtra;
+import android.media.soundtrigger.PhraseSoundModel;
+import android.media.soundtrigger.Properties;
+import android.media.soundtrigger.RecognitionConfig;
+import android.media.soundtrigger.RecognitionEvent;
+import android.media.soundtrigger.RecognitionMode;
+import android.media.soundtrigger.SoundModel;
 import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
-import android.media.soundtrigger_middleware.SoundTriggerModuleProperties;
 import android.os.ParcelFileDescriptor;
 import android.os.SharedMemory;
 
@@ -43,7 +43,7 @@
 class ConversionUtil {
     public static SoundTrigger.ModuleProperties aidl2apiModuleDescriptor(
             SoundTriggerModuleDescriptor aidlDesc) {
-        SoundTriggerModuleProperties properties = aidlDesc.properties;
+        Properties properties = aidlDesc.properties;
         return new SoundTrigger.ModuleProperties(
                 aidlDesc.handle,
                 properties.implementor,
@@ -194,19 +194,19 @@
     }
 
     public static SoundTrigger.RecognitionEvent aidl2apiRecognitionEvent(
-            int modelHandle, RecognitionEvent aidlEvent) {
+            int modelHandle, int captureSession, RecognitionEvent aidlEvent) {
         // The API recognition event doesn't allow for a null audio format, even though it doesn't
         // always make sense. We thus replace it with a default.
         AudioFormat audioFormat = aidl2apiAudioFormatWithDefault(aidlEvent.audioConfig);
         return new SoundTrigger.GenericRecognitionEvent(
                 aidlEvent.status,
-                modelHandle, aidlEvent.captureAvailable, aidlEvent.captureSession,
+                modelHandle, aidlEvent.captureAvailable, captureSession,
                 aidlEvent.captureDelayMs, aidlEvent.capturePreambleMs, aidlEvent.triggerInData,
                 audioFormat, aidlEvent.data);
     }
 
     public static SoundTrigger.RecognitionEvent aidl2apiPhraseRecognitionEvent(
-            int modelHandle,
+            int modelHandle, int captureSession,
             PhraseRecognitionEvent aidlEvent) {
         SoundTrigger.KeyphraseRecognitionExtra[] apiExtras =
                 new SoundTrigger.KeyphraseRecognitionExtra[aidlEvent.phraseExtras.length];
@@ -218,7 +218,7 @@
         AudioFormat audioFormat = aidl2apiAudioFormatWithDefault(aidlEvent.common.audioConfig);
         return new SoundTrigger.KeyphraseRecognitionEvent(aidlEvent.common.status, modelHandle,
                 aidlEvent.common.captureAvailable,
-                aidlEvent.common.captureSession, aidlEvent.common.captureDelayMs,
+                captureSession, aidlEvent.common.captureDelayMs,
                 aidlEvent.common.capturePreambleMs, aidlEvent.common.triggerInData,
                 audioFormat, aidlEvent.common.data,
                 apiExtras);
@@ -328,9 +328,9 @@
     public static int api2aidlModelParameter(int apiParam) {
         switch (apiParam) {
             case ModelParams.THRESHOLD_FACTOR:
-                return android.media.soundtrigger_middleware.ModelParameter.THRESHOLD_FACTOR;
+                return android.media.soundtrigger.ModelParameter.THRESHOLD_FACTOR;
             default:
-                return android.media.soundtrigger_middleware.ModelParameter.INVALID;
+                return android.media.soundtrigger.ModelParameter.INVALID;
         }
     }
 
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index 11f3e45..163e6f0 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -19,6 +19,7 @@
 import static android.Manifest.permission.CAPTURE_AUDIO_HOTWORD;
 import static android.Manifest.permission.RECORD_AUDIO;
 import static android.Manifest.permission.SOUNDTRIGGER_DELEGATE_IDENTITY;
+import static android.system.OsConstants.EBUSY;
 import static android.system.OsConstants.EINVAL;
 import static android.system.OsConstants.ENODEV;
 import static android.system.OsConstants.ENOSYS;
@@ -41,9 +42,9 @@
 import android.media.permission.ClearCallingIdentityContext;
 import android.media.permission.Identity;
 import android.media.permission.SafeCloseable;
+import android.media.soundtrigger.Status;
 import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
 import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
-import android.media.soundtrigger_middleware.Status;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Handler;
@@ -91,6 +92,8 @@
     public static final int STATUS_DEAD_OBJECT = -EPIPE;
     /** @hide */
     public static final int STATUS_INVALID_OPERATION = -ENOSYS;
+    /** @hide */
+    public static final int STATUS_BUSY = -EBUSY;
 
     /*****************************************************************************
      * A ModuleProperties describes a given sound trigger hardware module
@@ -1835,120 +1838,6 @@
         }
     }
 
-    /**
-     *  Status codes for {@link SoundModelEvent}
-     */
-    /**
-     * Sound Model was updated
-     *
-     * @hide
-     */
-    public static final int SOUNDMODEL_STATUS_UPDATED = 0;
-
-    /**
-     *  A SoundModelEvent is provided by the
-     *  {@link StatusListener#onSoundModelUpdate(SoundModelEvent)}
-     *  callback when a sound model has been updated by the implementation
-     *
-     *  @hide
-     */
-    public static class SoundModelEvent implements Parcelable {
-        /** Status e.g {@link #SOUNDMODEL_STATUS_UPDATED} */
-        public final int status;
-        /** The updated sound model handle */
-        public final int soundModelHandle;
-        /** New sound model data */
-        @NonNull
-        public final byte[] data;
-
-        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-        SoundModelEvent(int status, int soundModelHandle, @Nullable byte[] data) {
-            this.status = status;
-            this.soundModelHandle = soundModelHandle;
-            this.data = data != null ? data : new byte[0];
-        }
-
-        public static final @android.annotation.NonNull Parcelable.Creator<SoundModelEvent> CREATOR
-                = new Parcelable.Creator<SoundModelEvent>() {
-            public SoundModelEvent createFromParcel(Parcel in) {
-                return SoundModelEvent.fromParcel(in);
-            }
-
-            public SoundModelEvent[] newArray(int size) {
-                return new SoundModelEvent[size];
-            }
-        };
-
-        private static SoundModelEvent fromParcel(Parcel in) {
-            int status = in.readInt();
-            int soundModelHandle = in.readInt();
-            byte[] data = in.readBlob();
-            return new SoundModelEvent(status, soundModelHandle, data);
-        }
-
-        @Override
-        public int describeContents() {
-            return 0;
-        }
-
-        @Override
-        public void writeToParcel(Parcel dest, int flags) {
-            dest.writeInt(status);
-            dest.writeInt(soundModelHandle);
-            dest.writeBlob(data);
-        }
-
-        @Override
-        public int hashCode() {
-            final int prime = 31;
-            int result = 1;
-            result = prime * result + Arrays.hashCode(data);
-            result = prime * result + soundModelHandle;
-            result = prime * result + status;
-            return result;
-        }
-
-        @Override
-        public boolean equals(@Nullable Object obj) {
-            if (this == obj)
-                return true;
-            if (obj == null)
-                return false;
-            if (getClass() != obj.getClass())
-                return false;
-            SoundModelEvent other = (SoundModelEvent) obj;
-            if (!Arrays.equals(data, other.data))
-                return false;
-            if (soundModelHandle != other.soundModelHandle)
-                return false;
-            if (status != other.status)
-                return false;
-            return true;
-        }
-
-        @Override
-        public String toString() {
-            return "SoundModelEvent [status=" + status + ", soundModelHandle=" + soundModelHandle
-                    + ", data=" + (data == null ? 0 : data.length) + "]";
-        }
-    }
-
-    /**
-     *  Native service state. {@link StatusListener#onServiceStateChange(int)}
-     */
-    // Keep in sync with system/core/include/system/sound_trigger.h
-    /**
-     * Sound trigger service is enabled
-     *
-     * @hide
-     */
-    public static final int SERVICE_STATE_ENABLED = 0;
-    /**
-     * Sound trigger service is disabled
-     *
-     * @hide
-     */
-    public static final int SERVICE_STATE_DISABLED = 1;
     private static Object mServiceLock = new Object();
     private static ISoundTriggerMiddlewareService mService;
 
@@ -1975,6 +1864,8 @@
                     return STATUS_DEAD_OBJECT;
                 case Status.INTERNAL_ERROR:
                     return STATUS_ERROR;
+                case Status.RESOURCE_CONTENTION:
+                    return STATUS_BUSY;
             }
             return STATUS_ERROR;
         }
@@ -2224,27 +2115,28 @@
      *
      * @hide
      */
-    public static interface StatusListener {
+    public interface StatusListener {
         /**
          * Called when recognition succeeds of fails
          */
-        public abstract void onRecognition(RecognitionEvent event);
+        void onRecognition(RecognitionEvent event);
 
         /**
-         * Called when a sound model has been updated
+         * Called when a sound model has been preemptively unloaded by the underlying
+         * implementation.
          */
-        public abstract void onSoundModelUpdate(SoundModelEvent event);
+        void onModelUnloaded(int modelHandle);
 
         /**
-         * Called when the sound trigger native service state changes.
-         * @param state Native service state. One of {@link SoundTrigger#SERVICE_STATE_ENABLED},
-         * {@link SoundTrigger#SERVICE_STATE_DISABLED}
+         * Called whenever underlying conditions change, such that load/start operations that have
+         * previously failed or got preempted may now succeed. This is not a guarantee, merely a
+         * hint that the client may want to retry operations.
          */
-        public abstract void onServiceStateChange(int state);
+        void onResourcesAvailable();
 
         /**
          * Called when the sound trigger native service dies
          */
-        public abstract void onServiceDied();
+        void onServiceDied();
     }
 }
diff --git a/core/java/android/hardware/soundtrigger/SoundTriggerModule.java b/core/java/android/hardware/soundtrigger/SoundTriggerModule.java
index 431c99d..bf4b514 100644
--- a/core/java/android/hardware/soundtrigger/SoundTriggerModule.java
+++ b/core/java/android/hardware/soundtrigger/SoundTriggerModule.java
@@ -22,13 +22,13 @@
 import android.media.permission.ClearCallingIdentityContext;
 import android.media.permission.Identity;
 import android.media.permission.SafeCloseable;
+import android.media.soundtrigger.PhraseRecognitionEvent;
+import android.media.soundtrigger.PhraseSoundModel;
+import android.media.soundtrigger.RecognitionEvent;
+import android.media.soundtrigger.SoundModel;
 import android.media.soundtrigger_middleware.ISoundTriggerCallback;
 import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
 import android.media.soundtrigger_middleware.ISoundTriggerModule;
-import android.media.soundtrigger_middleware.PhraseRecognitionEvent;
-import android.media.soundtrigger_middleware.PhraseSoundModel;
-import android.media.soundtrigger_middleware.RecognitionEvent;
-import android.media.soundtrigger_middleware.SoundModel;
 import android.os.Build;
 import android.os.Handler;
 import android.os.IBinder;
@@ -48,7 +48,8 @@
 
     private static final int EVENT_RECOGNITION = 1;
     private static final int EVENT_SERVICE_DIED = 2;
-    private static final int EVENT_SERVICE_STATE_CHANGE = 3;
+    private static final int EVENT_RESOURCES_AVAILABLE = 3;
+    private static final int EVENT_MODEL_UNLOADED = 4;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private int mId;
     private EventHandlerDelegate mEventHandlerDelegate;
@@ -120,6 +121,7 @@
      * @param soundModelHandle an array of int where the sound model handle will be returned.
      * @return - {@link SoundTrigger#STATUS_OK} in case of success
      *         - {@link SoundTrigger#STATUS_ERROR} in case of unspecified error
+     *         - {@link SoundTrigger#STATUS_BUSY} in case of transient resource constraints
      *         - {@link SoundTrigger#STATUS_PERMISSION_DENIED} if the caller does not have
      *         system permission
      *         - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached
@@ -181,6 +183,7 @@
      *  recognition mode, keyphrases, users, minimum confidence levels...
      * @return - {@link SoundTrigger#STATUS_OK} in case of success
      *         - {@link SoundTrigger#STATUS_ERROR} in case of unspecified error
+     *         - {@link SoundTrigger#STATUS_BUSY} in case of transient resource constraints
      *         - {@link SoundTrigger#STATUS_PERMISSION_DENIED} if the caller does not have
      *         system permission
      *         - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached
@@ -333,8 +336,11 @@
                             listener.onRecognition(
                                     (SoundTrigger.RecognitionEvent) msg.obj);
                             break;
-                        case EVENT_SERVICE_STATE_CHANGE:
-                            listener.onServiceStateChange((int) msg.obj);
+                        case EVENT_RESOURCES_AVAILABLE:
+                            listener.onResourcesAvailable();
+                            break;
+                        case EVENT_MODEL_UNLOADED:
+                            listener.onModelUnloaded((Integer) msg.obj);
                             break;
                         case EVENT_SERVICE_DIED:
                             listener.onServiceDied();
@@ -348,27 +354,32 @@
         }
 
         @Override
-        public synchronized void onRecognition(int handle, RecognitionEvent event)
+        public synchronized void onRecognition(int handle, RecognitionEvent event,
+                int captureSession)
                 throws RemoteException {
             Message m = mHandler.obtainMessage(EVENT_RECOGNITION,
-                    ConversionUtil.aidl2apiRecognitionEvent(handle, event));
+                    ConversionUtil.aidl2apiRecognitionEvent(handle, captureSession, event));
             mHandler.sendMessage(m);
         }
 
         @Override
-        public synchronized void onPhraseRecognition(int handle, PhraseRecognitionEvent event)
+        public synchronized void onPhraseRecognition(int handle, PhraseRecognitionEvent event,
+                int captureSession)
                 throws RemoteException {
             Message m = mHandler.obtainMessage(EVENT_RECOGNITION,
-                    ConversionUtil.aidl2apiPhraseRecognitionEvent(handle, event));
+                    ConversionUtil.aidl2apiPhraseRecognitionEvent(handle, captureSession, event));
             mHandler.sendMessage(m);
         }
 
         @Override
-        public synchronized void onRecognitionAvailabilityChange(boolean available)
-                throws RemoteException {
-            Message m = mHandler.obtainMessage(EVENT_SERVICE_STATE_CHANGE,
-                    available ? SoundTrigger.SERVICE_STATE_ENABLED
-                            : SoundTrigger.SERVICE_STATE_DISABLED);
+        public void onModelUnloaded(int modelHandle) throws RemoteException {
+            Message m = mHandler.obtainMessage(EVENT_MODEL_UNLOADED, modelHandle);
+            mHandler.sendMessage(m);
+        }
+
+        @Override
+        public synchronized void onResourcesAvailable() throws RemoteException {
+            Message m = mHandler.obtainMessage(EVENT_RESOURCES_AVAILABLE);
             mHandler.sendMessage(m);
         }
 
diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java
index 964d7c1..c29a948 100644
--- a/core/java/android/hardware/usb/UsbManager.java
+++ b/core/java/android/hardware/usb/UsbManager.java
@@ -384,8 +384,9 @@
             "android.hardware.usb.extra.ACCESSORY_START";
 
     /**
-     * A long extra indicating ms from boot to sent {@link #ACTION_USB_ACCESSORY_HANDSHAKE}
-     * This is obtained with SystemClock.elapsedRealtime()
+
+     * A long extra indicating the timestamp just before
+     * sending {@link #ACTION_USB_ACCESSORY_HANDSHAKE}.
      * Used in extras for {@link #ACTION_USB_ACCESSORY_HANDSHAKE} broadcasts.
      *
      * {@hide}
diff --git a/core/java/android/os/BinderProxy.java b/core/java/android/os/BinderProxy.java
index 16d041a..d026e95 100644
--- a/core/java/android/os/BinderProxy.java
+++ b/core/java/android/os/BinderProxy.java
@@ -34,6 +34,9 @@
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
 
 /**
  * Java proxy for a native IBinder object.
@@ -262,27 +265,45 @@
                 Log.e(Binder.TAG, "RemoteException while disabling app freezer");
             }
 
-            for (WeakReference<BinderProxy> weakRef : proxiesToQuery) {
-                BinderProxy bp = weakRef.get();
-                String key;
-                if (bp == null) {
-                    key = "<cleared weak-ref>";
-                } else {
-                    try {
-                        key = bp.getInterfaceDescriptor();
-                        if ((key == null || key.isEmpty()) && !bp.isBinderAlive()) {
-                            key = "<proxy to dead node>";
+            // We run the dump on a separate thread, because there are known cases where
+            // a process overrides getInterfaceDescriptor() and somehow blocks on it, causing
+            // the calling thread (usually AMS) to hit the watchdog.
+            // Do the dumping on a separate thread instead, and give up after a while.
+            ExecutorService executorService = Executors.newSingleThreadExecutor();
+            executorService.submit(() -> {
+                for (WeakReference<BinderProxy> weakRef : proxiesToQuery) {
+                    BinderProxy bp = weakRef.get();
+                    String key;
+                    if (bp == null) {
+                        key = "<cleared weak-ref>";
+                    } else {
+                        try {
+                            key = bp.getInterfaceDescriptor();
+                            if ((key == null || key.isEmpty()) && !bp.isBinderAlive()) {
+                                key = "<proxy to dead node>";
+                            }
+                        } catch (Throwable t) {
+                            key = "<exception during getDescriptor>";
                         }
-                    } catch (Throwable t) {
-                        key = "<exception during getDescriptor>";
+                    }
+                    Integer i = counts.get(key);
+                    if (i == null) {
+                        counts.put(key, 1);
+                    } else {
+                        counts.put(key, i + 1);
                     }
                 }
-                Integer i = counts.get(key);
-                if (i == null) {
-                    counts.put(key, 1);
-                } else {
-                    counts.put(key, i + 1);
+            });
+
+            try {
+                executorService.shutdown();
+                boolean dumpDone = executorService.awaitTermination(20, TimeUnit.SECONDS);
+                if (!dumpDone) {
+                    Log.e(Binder.TAG, "Failed to complete binder proxy dump,"
+                            + " dumping what we have so far.");
                 }
+            } catch (InterruptedException e) {
+                // Ignore
             }
             try {
                 ActivityManager.getService().enableAppFreezer(true);
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 834ae33..d5750d4 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -1351,7 +1351,11 @@
     public static final boolean IS_USER = "user".equals(TYPE);
 
     /**
-     * Whether this build is running inside a container.
+     * Whether this build is running on ARC, the Android Runtime for Chrome
+     * (https://chromium.googlesource.com/chromiumos/docs/+/master/containers_and_vms.md).
+     * Prior to R this was implemented as a container but from R this will be
+     * a VM. The name of the property remains ro.boot.conntainer as it is
+     * referenced in other projects.
      *
      * We should try to avoid checking this flag if possible to minimize
      * unnecessarily diverging from non-container Android behavior.
@@ -1362,7 +1366,7 @@
      * For higher-level behavior differences, other checks should be preferred.
      * @hide
      */
-    public static final boolean IS_CONTAINER =
+    public static final boolean IS_ARC =
             SystemProperties.getBoolean("ro.boot.container", false);
 
     /**
diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java
index 44c3d61..7455f1f 100644
--- a/core/java/android/os/StrictMode.java
+++ b/core/java/android/os/StrictMode.java
@@ -115,20 +115,18 @@
  *
  * <pre>
  * public void onCreate() {
- *     if (DEVELOPER_MODE) {
- *         StrictMode.setThreadPolicy(new {@link ThreadPolicy.Builder StrictMode.ThreadPolicy.Builder}()
- *                 .detectDiskReads()
- *                 .detectDiskWrites()
- *                 .detectNetwork()   // or .detectAll() for all detectable problems
- *                 .penaltyLog()
- *                 .build());
- *         StrictMode.setVmPolicy(new {@link VmPolicy.Builder StrictMode.VmPolicy.Builder}()
- *                 .detectLeakedSqlLiteObjects()
- *                 .detectLeakedClosableObjects()
- *                 .penaltyLog()
- *                 .penaltyDeath()
- *                 .build());
- *     }
+ *     StrictMode.setThreadPolicy(new {@link ThreadPolicy.Builder StrictMode.ThreadPolicy.Builder}()
+ *             .detectDiskReads()
+ *             .detectDiskWrites()
+ *             .detectNetwork()   // or .detectAll() for all detectable problems
+ *             .penaltyLog()
+ *             .build());
+ *     StrictMode.setVmPolicy(new {@link VmPolicy.Builder StrictMode.VmPolicy.Builder}()
+ *             .detectLeakedSqlLiteObjects()
+ *             .detectLeakedClosableObjects()
+ *             .penaltyLog()
+ *             .penaltyDeath()
+ *             .build());
  *     super.onCreate();
  * }
  * </pre>
@@ -147,9 +145,7 @@
  * <p class="note">StrictMode is not a security mechanism and is not guaranteed to find all disk or
  * network accesses. While it does propagate its state across process boundaries when doing {@link
  * android.os.Binder} calls, it's still ultimately a best effort mechanism. Notably, disk or network
- * access from JNI calls won't necessarily trigger it. Future versions of Android may catch more (or
- * fewer) operations, so you should never leave StrictMode enabled in applications distributed on
- * Google Play.
+ * access from JNI calls won't necessarily trigger it.
  */
 public final class StrictMode {
     private static final String TAG = "StrictMode";
diff --git a/core/java/android/os/storage/IStorageManager.aidl b/core/java/android/os/storage/IStorageManager.aidl
index 9385402..fbac954 100644
--- a/core/java/android/os/storage/IStorageManager.aidl
+++ b/core/java/android/os/storage/IStorageManager.aidl
@@ -198,9 +198,9 @@
     void clearUserKeyAuth(int userId, int serialNumber, in byte[] token, in byte[] secret) = 88;
     void fixupAppDir(in String path) = 89;
     void disableAppDataIsolation(in String pkgName, int pid, int userId) = 90;
-    void notifyAppIoBlocked(in String volumeUuid, int uid, int tid, int reason) = 91;
-    void notifyAppIoResumed(in String volumeUuid, int uid, int tid, int reason) = 92;
-    PendingIntent getManageSpaceActivityIntent(in String packageName, int requestCode) = 93;
-    boolean isAppIoBlocked(in String volumeUuid, int uid, int tid, int reason) = 94;
-    int getExternalStorageMountMode(int uid, in String packageName) = 95;
-}
+    PendingIntent getManageSpaceActivityIntent(in String packageName, int requestCode) = 91;
+    void notifyAppIoBlocked(in String volumeUuid, int uid, int tid, int reason) = 92;
+    void notifyAppIoResumed(in String volumeUuid, int uid, int tid, int reason) = 93;
+    int getExternalStorageMountMode(int uid, in String packageName) = 94;
+    boolean isAppIoBlocked(in String volumeUuid, int uid, int tid, int reason) = 95;
+}
\ No newline at end of file
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index d7d1902..d55df21 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -423,6 +423,15 @@
     public static final String NAMESPACE_STORAGE_NATIVE_BOOT = "storage_native_boot";
 
     /**
+     * Namespace for swcodec native related features.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String NAMESPACE_SWCODEC_NATIVE = "swcodec_native";
+
+
+    /**
      * Namespace for System UI related features.
      *
      * @hide
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index f3a8b5d..387fc39 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -5343,6 +5343,13 @@
         public static final String COLUMN_RCS_CONFIG = "rcs_config";
 
         /**
+         * TelephonyProvider column name for device to device sharing status.
+         *
+         * @hide
+         */
+        public static final String COLUMN_D2D_STATUS_SHARING = "d2d_sharing_status";
+
+        /**
          * TelephonyProvider column name for VoIMS provisioning. Default is 0.
          * <P>Type: INTEGER </P>
          *
@@ -5351,13 +5358,6 @@
         public static final String COLUMN_VOIMS_OPT_IN_STATUS = "voims_opt_in_status";
 
         /**
-         * TelephonyProvider column name for device to device sharing status.
-         *
-         * @hide
-         */
-        public static final String COLUMN_D2D_STATUS_SHARING = "d2d_sharing_status";
-
-        /**
          * TelephonyProvider column name for information selected contacts that allow device to
          * device sharing.
          *
@@ -5365,5 +5365,6 @@
          */
         public static final String COLUMN_D2D_STATUS_SHARING_SELECTED_CONTACTS =
                 "d2d_sharing_contacts";
+
     }
 }
diff --git a/core/java/android/service/notification/NotificationAssistantService.java b/core/java/android/service/notification/NotificationAssistantService.java
index 4fd36e5..91042bfa 100644
--- a/core/java/android/service/notification/NotificationAssistantService.java
+++ b/core/java/android/service/notification/NotificationAssistantService.java
@@ -91,7 +91,8 @@
             = "android.service.notification.NotificationAssistantService";
 
     /**
-     * Data type: int, the feedback rating score provided by user
+     * Data type: int, the feedback rating score provided by user. The score can be any integer
+     *            value depends on the experimental and feedback UX design.
      */
     public static final String FEEDBACK_RATING = "feedback.rating";
 
@@ -129,7 +130,8 @@
      * A notification was posted by an app. Called before post.
      *
      * <p>Note: this method is only called if you don't override
-     * {@link #onNotificationEnqueued(StatusBarNotification, NotificationChannel)}.</p>
+     * {@link #onNotificationEnqueued(StatusBarNotification, NotificationChannel)} or
+     * {@link #onNotificationEnqueued(StatusBarNotification, NotificationChannel, RankingMap)}.</p>
      *
      * @param sbn the new notification
      * @return an adjustment or null to take no action, within 200ms.
@@ -139,6 +141,9 @@
     /**
      * A notification was posted by an app. Called before post.
      *
+     * <p>Note: this method is only called if you don't override
+     * {@link #onNotificationEnqueued(StatusBarNotification, NotificationChannel, RankingMap)}.</p>
+     *
      * @param sbn the new notification
      * @param channel the channel the notification was posted to
      * @return an adjustment or null to take no action, within 200ms.
@@ -282,7 +287,7 @@
      * @param key the notification key
      * @param rankingMap The current ranking map that can be used to retrieve ranking information
      *                   for active notifications.
-     * @param feedback the feedback detail
+     * @param feedback the received feedback, such as {@link #FEEDBACK_RATING rating score}
      */
     public void onNotificationFeedbackReceived(@NonNull String key, @NonNull RankingMap rankingMap,
             @NonNull Bundle feedback) {
diff --git a/core/java/android/util/apk/ApkSignatureVerifier.java b/core/java/android/util/apk/ApkSignatureVerifier.java
index 696271c..f0ce325 100644
--- a/core/java/android/util/apk/ApkSignatureVerifier.java
+++ b/core/java/android/util/apk/ApkSignatureVerifier.java
@@ -23,10 +23,10 @@
 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION;
 import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
 
-import android.content.pm.PackageParser;
 import android.content.pm.PackageParser.PackageParserException;
-import android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion;
 import android.content.pm.Signature;
+import android.content.pm.SigningDetails;
+import android.content.pm.SigningDetails.SignatureSchemeVersion;
 import android.content.pm.parsing.ParsingPackageUtils;
 import android.os.Build;
 import android.os.Trace;
@@ -65,7 +65,7 @@
      *
      * @throws PackageParserException if the APK's signature failed to verify.
      */
-    public static PackageParser.SigningDetails verify(String apkPath,
+    public static SigningDetails verify(String apkPath,
             @SignatureSchemeVersion int minSignatureSchemeVersion)
             throws PackageParserException {
         return verifySignatures(apkPath, minSignatureSchemeVersion, true);
@@ -78,7 +78,7 @@
      *
      * @throws PackageParserException if there was a problem collecting certificates.
      */
-    public static PackageParser.SigningDetails unsafeGetCertsWithoutVerification(
+    public static SigningDetails unsafeGetCertsWithoutVerification(
             String apkPath, int minSignatureSchemeVersion)
             throws PackageParserException {
         return verifySignatures(apkPath, minSignatureSchemeVersion, false);
@@ -90,7 +90,7 @@
      * @param verifyFull whether to verify all contents of this APK or just collect certificates.
      * @throws PackageParserException if there was a problem collecting certificates
      */
-    private static PackageParser.SigningDetails verifySignatures(String apkPath,
+    private static SigningDetails verifySignatures(String apkPath,
             @SignatureSchemeVersion int minSignatureSchemeVersion, boolean verifyFull)
             throws PackageParserException {
         return verifySignaturesInternal(apkPath, minSignatureSchemeVersion,
@@ -249,7 +249,7 @@
                 }
             }
 
-            return new SigningDetailsWithDigests(new PackageParser.SigningDetails(signerSigs,
+            return new SigningDetailsWithDigests(new SigningDetails(signerSigs,
                     SignatureSchemeVersion.SIGNING_BLOCK_V4), vSigner.contentDigests);
         } catch (SignatureNotFoundException e) {
             throw e;
@@ -290,7 +290,7 @@
                     pastSignerSigs[i].setFlags(vSigner.por.flagsList.get(i));
                 }
             }
-            return new SigningDetailsWithDigests(new PackageParser.SigningDetails(signerSigs,
+            return new SigningDetailsWithDigests(new SigningDetails(signerSigs,
                     SignatureSchemeVersion.SIGNING_BLOCK_V3, pastSignerSigs),
                     vSigner.contentDigests);
         } catch (SignatureNotFoundException e) {
@@ -321,7 +321,7 @@
                     ApkSignatureSchemeV2Verifier.verify(apkPath, verifyFull);
             Certificate[][] signerCerts = vSigner.certs;
             Signature[] signerSigs = convertToSignatures(signerCerts);
-            return new SigningDetailsWithDigests(new PackageParser.SigningDetails(signerSigs,
+            return new SigningDetailsWithDigests(new SigningDetails(signerSigs,
                     SignatureSchemeVersion.SIGNING_BLOCK_V2), vSigner.contentDigests);
         } catch (SignatureNotFoundException e) {
             throw e;
@@ -408,7 +408,7 @@
                 }
             }
             return new SigningDetailsWithDigests(
-                    new PackageParser.SigningDetails(lastSigs, SignatureSchemeVersion.JAR), null);
+                    new SigningDetails(lastSigs, SignatureSchemeVersion.JAR), null);
         } catch (GeneralSecurityException e) {
             throw new PackageParserException(INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING,
                     "Failed to collect certificates from " + apkPath, e);
@@ -565,7 +565,7 @@
      * @hide for internal use only.
      */
     public static class SigningDetailsWithDigests {
-        public final PackageParser.SigningDetails signingDetails;
+        public final SigningDetails signingDetails;
 
         /**
          * APK Signature Schemes v2/v3/v4 might contain multiple content digests.
@@ -576,7 +576,7 @@
          */
         public final Map<Integer, byte[]> contentDigests;
 
-        SigningDetailsWithDigests(PackageParser.SigningDetails signingDetails,
+        SigningDetailsWithDigests(SigningDetails signingDetails,
                 Map<Integer, byte[]> contentDigests) {
             this.signingDetails = signingDetails;
             this.contentDigests = contentDigests;
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 5b695f4..6db5eae 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -2723,7 +2723,7 @@
                                     continue;
                                 }
                                 childWithAccessibilityFocus = null;
-                                i = childrenCount - 1;
+                                i = childrenCount;
                             }
 
                             if (!child.canReceivePointerEvents()
diff --git a/core/java/android/widget/AdapterViewAnimator.java b/core/java/android/widget/AdapterViewAnimator.java
index d8f7f4c..f3dfda5 100644
--- a/core/java/android/widget/AdapterViewAnimator.java
+++ b/core/java/android/widget/AdapterViewAnimator.java
@@ -412,24 +412,29 @@
     }
 
     void refreshChildren() {
-        if (mAdapter == null) return;
+        final int adapterCount = mAdapter == null ? 0 : getCount();
         for (int i = mCurrentWindowStart; i <= mCurrentWindowEnd; i++) {
             int index = modulo(i, getWindowSize());
 
-            int adapterCount = getCount();
-            // get the fresh child from the adapter
-            final View updatedChild = mAdapter.getView(modulo(i, adapterCount), null, this);
+            final View updatedChild;
+            if (i < adapterCount) {
+                // get the fresh child from the adapter
+                updatedChild = mAdapter.getView(modulo(i, adapterCount), null, this);
 
-            if (updatedChild.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
-                updatedChild.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+                if (updatedChild.getImportantForAccessibility()
+                        == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
+                    updatedChild.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+                }
+            } else {
+                updatedChild = null;
             }
 
             if (mViewsMap.containsKey(index)) {
                 final FrameLayout fl = (FrameLayout) mViewsMap.get(index).view;
-                // add the new child to the frame, if it exists
+                // flush out the old child
+                fl.removeAllViewsInLayout();
                 if (updatedChild != null) {
-                    // flush out the old child
-                    fl.removeAllViewsInLayout();
+                    // add the new child to the frame, if it exists
                     fl.addView(updatedChild);
                 }
             }
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 862c900..d408704 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -2119,10 +2119,10 @@
                     + " resultList.size()=" + resultList.size()
                     + " appTargets.size()=" + appTargets.size());
         }
-
+        Context selectedProfileContext = createContextAsUser(userHandle, 0 /* flags */);
         for (int i = resultList.size() - 1; i >= 0; i--) {
             final String packageName = resultList.get(i).getTargetComponent().getPackageName();
-            if (!isPackageEnabled(packageName)) {
+            if (!isPackageEnabled(selectedProfileContext, packageName)) {
                 resultList.remove(i);
                 if (appTargets != null) {
                     appTargets.remove(i);
@@ -2174,13 +2174,13 @@
         mChooserHandler.sendMessage(msg);
     }
 
-    private boolean isPackageEnabled(String packageName) {
+    private boolean isPackageEnabled(Context context, String packageName) {
         if (TextUtils.isEmpty(packageName)) {
             return false;
         }
         ApplicationInfo appInfo;
         try {
-            appInfo = getPackageManager().getApplicationInfo(packageName, 0);
+            appInfo = context.getPackageManager().getApplicationInfo(packageName, 0);
         } catch (NameNotFoundException e) {
             return false;
         }
diff --git a/core/java/com/android/internal/os/BinderLatencyBuckets.java b/core/java/com/android/internal/os/BinderLatencyBuckets.java
new file mode 100644
index 0000000..d7d2d6a
--- /dev/null
+++ b/core/java/com/android/internal/os/BinderLatencyBuckets.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.os;
+
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Arrays;
+
+/**
+ * Generates the bucket thresholds (with a custom logarithmic scale) for a histogram to store
+ * latency samples in.
+ */
+public class BinderLatencyBuckets {
+    private static final String TAG = "BinderLatencyBuckets";
+    private final int[] mBuckets;
+
+    /**
+     * @param bucketCount      the number of buckets the histogram should have
+     * @param firstBucketSize  the size of the first bucket (used to avoid excessive small buckets)
+     * @param scaleFactor      the rate in which each consecutive bucket increases (before rounding)
+     */
+    public BinderLatencyBuckets(int bucketCount, int firstBucketSize, float scaleFactor) {
+        int[] buffer = new int[bucketCount - 1];
+        buffer[0] = firstBucketSize;
+
+        // Last value and the target are disjoint as we never want to create buckets smaller than 1.
+        double lastTarget = firstBucketSize;
+
+        // First bucket is already created and the last bucket is anything greater than the final
+        // bucket in the list, so create 'bucketCount' - 2 buckets.
+        for (int i = 1; i < bucketCount - 1; i++) {
+            // Increase the target bucket limit value by the scale factor.
+            double nextTarget = lastTarget * scaleFactor;
+
+            if (nextTarget > Integer.MAX_VALUE) {
+                // Do not throw an exception here as this should not affect binder calls.
+                Slog.w(TAG, "Attempted to create a bucket larger than maxint");
+                mBuckets = Arrays.copyOfRange(buffer, 0, i);
+                return;
+            }
+
+            if ((int) nextTarget > buffer[i - 1]) {
+                // Convert the target bucket limit value to an integer.
+                buffer[i] = (int) nextTarget;
+            } else {
+                // Avoid creating redundant buckets, so bucket size should be 1 at a minimum.
+                buffer[i] = buffer[i - 1] + 1;
+            }
+            lastTarget = nextTarget;
+        }
+        mBuckets = buffer;
+    }
+
+    /** Gets the bucket index to insert the provided sample in. */
+    public int sampleToBucket(int sample) {
+        if (sample >= mBuckets[mBuckets.length - 1]) {
+            return mBuckets.length;
+        }
+
+        // Binary search returns the element index if it is contained in the list - in this case the
+        // correct bucket is the index after as we use [minValue, maxValue) for bucket boundaries.
+        // Otherwise, it returns (-(insertion point) - 1), where insertion point is the point where
+        // to insert the element so that the array remains sorted - in this case the bucket index
+        // is the insertion point.
+        int searchResult = Arrays.binarySearch(mBuckets, sample);
+        return searchResult < 0 ? -(1 + searchResult) : searchResult + 1;
+    }
+
+    @VisibleForTesting
+    public int[] getBuckets() {
+        return mBuckets;
+    }
+}
diff --git a/core/java/com/android/internal/os/BinderLatencyObserver.java b/core/java/com/android/internal/os/BinderLatencyObserver.java
index 92b4952..4ca59be 100644
--- a/core/java/com/android/internal/os/BinderLatencyObserver.java
+++ b/core/java/com/android/internal/os/BinderLatencyObserver.java
@@ -16,46 +16,173 @@
 
 package com.android.internal.os;
 
+import static com.android.internal.os.BinderLatencyProto.Dims.SYSTEM_SERVER;
+
 import android.annotation.Nullable;
 import android.os.Binder;
+import android.os.Handler;
+import android.os.Looper;
 import android.os.SystemClock;
 import android.util.ArrayMap;
 import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.BinderInternal.CallSession;
+import com.android.internal.os.BinderLatencyProto.ApiStats;
+import com.android.internal.os.BinderLatencyProto.Dims;
+import com.android.internal.os.BinderLatencyProto.RepeatedApiStats;
+import com.android.internal.util.FrameworkStatsLog;
 
-import java.util.ArrayList;
 import java.util.Random;
 
 /** Collects statistics about Binder call latency per calling API and method. */
 public class BinderLatencyObserver {
     private static final String TAG = "BinderLatencyObserver";
-    public static final int PERIODIC_SAMPLING_INTERVAL_DEFAULT = 10;
+    private static final int MAX_ATOM_SIZE_BYTES = 4064;
+    // Be conservative and leave 1K space for the last histogram so we don't go over the size limit.
+    private static final int LAST_HISTOGRAM_BUFFER_SIZE_BYTES = 1000;
 
-    // This is not the final data structure - we will eventually store latency histograms instead of
-    // raw samples as it is much more memory / disk space efficient.
-    // TODO(b/179999191): change this to store the histogram.
-    // TODO(b/179999191): pre allocate the array size so we would not have to resize this.
+    // Latency observer parameters.
+    public static final int PERIODIC_SAMPLING_INTERVAL_DEFAULT = 10;
+    public static final int STATSD_PUSH_INTERVAL_MINUTES_DEFAULT = 360;
+
+    // Histogram buckets parameters.
+    public static final int BUCKET_COUNT_DEFAULT = 100;
+    public static final int FIRST_BUCKET_SIZE_DEFAULT = 5;
+    public static final float BUCKET_SCALE_FACTOR_DEFAULT = 1.125f;
+
     @GuardedBy("mLock")
-    private final ArrayMap<LatencyDims, ArrayList<Long>> mLatencySamples = new ArrayMap<>();
+    private final ArrayMap<LatencyDims, int[]> mLatencyHistograms = new ArrayMap<>();
     private final Object mLock = new Object();
 
     // Sampling period to control how often to track CPU usage. 1 means all calls, 100 means ~1 out
     // of 100 requests.
     private int mPeriodicSamplingInterval = PERIODIC_SAMPLING_INTERVAL_DEFAULT;
+
+    private int mBucketCount = BUCKET_COUNT_DEFAULT;
+    private int mFirstBucketSize = FIRST_BUCKET_SIZE_DEFAULT;
+    private float mBucketScaleFactor = BUCKET_SCALE_FACTOR_DEFAULT;
+
+    private int mStatsdPushIntervalMinutes = STATSD_PUSH_INTERVAL_MINUTES_DEFAULT;
+
     private final Random mRandom;
+    private BinderLatencyBuckets mLatencyBuckets;
+
+    private final Handler mLatencyObserverHandler;
+
+    private Runnable mLatencyObserverRunnable = new Runnable() {
+        @Override
+        public void run() {
+            // Schedule the next push.
+            noteLatencyDelayed();
+
+            ArrayMap<LatencyDims, int[]> histogramMap;
+            synchronized (mLock) {
+                // Copy the histograms map so we don't use the lock for longer than needed.
+                histogramMap = new ArrayMap<>(mLatencyHistograms);
+                mLatencyHistograms.clear();
+            }
+
+            BinderTransactionNameResolver resolver = new BinderTransactionNameResolver();
+            ProtoOutputStream proto = new ProtoOutputStream();
+            int histogramsWritten = 0;
+
+            for (LatencyDims dims : histogramMap.keySet()) {
+                // Start a new atom if the next histogram risks going over the atom size limit.
+                if (proto.getRawSize() + LAST_HISTOGRAM_BUFFER_SIZE_BYTES > getMaxAtomSizeBytes()) {
+                    if (histogramsWritten > 0) {
+                        writeAtomToStatsd(proto);
+                    }
+                    proto = new ProtoOutputStream();
+                    histogramsWritten = 0;
+                }
+
+                String transactionName = resolver.getMethodName(
+                        dims.getBinderClass(), dims.getTransactionCode());
+                fillApiStatsProto(proto, dims, transactionName, histogramMap.get(dims));
+                histogramsWritten++;
+            }
+            // Push the final atom.
+            if (histogramsWritten > 0) {
+                writeAtomToStatsd(proto);
+            }
+        }
+    };
+
+    private void fillApiStatsProto(
+            ProtoOutputStream proto, LatencyDims dims, String transactionName, int[] histogram) {
+        // Find the part of the histogram to write.
+        int firstNonEmptyBucket = 0;
+        for (int i = 0; i < mBucketCount; i++) {
+            if (histogram[i] != 0) {
+                firstNonEmptyBucket = i;
+                break;
+            }
+        }
+        int lastNonEmptyBucket = mBucketCount - 1;
+        for (int i = mBucketCount - 1; i >= 0; i--) {
+            if (histogram[i] != 0) {
+                lastNonEmptyBucket = i;
+                break;
+            }
+        }
+
+        // Start a new ApiStats proto.
+        long apiStatsToken = proto.start(RepeatedApiStats.API_STATS);
+
+        // Write the dims.
+        long dimsToken = proto.start(ApiStats.DIMS);
+        proto.write(Dims.PROCESS_SOURCE, SYSTEM_SERVER);
+        proto.write(Dims.SERVICE_CLASS_NAME, dims.getBinderClass().getName());
+        proto.write(Dims.SERVICE_METHOD_NAME, transactionName);
+        proto.end(dimsToken);
+
+        // Write the histogram.
+        proto.write(ApiStats.FIRST_BUCKET_INDEX, firstNonEmptyBucket);
+        for (int i = firstNonEmptyBucket; i <= lastNonEmptyBucket; i++) {
+            proto.write(ApiStats.BUCKETS, histogram[i]);
+        }
+
+        proto.end(apiStatsToken);
+    }
+
+    protected int getMaxAtomSizeBytes() {
+        return MAX_ATOM_SIZE_BYTES;
+    }
+
+    protected void writeAtomToStatsd(ProtoOutputStream atom) {
+        FrameworkStatsLog.write(
+                FrameworkStatsLog.BINDER_LATENCY_REPORTED,
+                atom.getBytes(),
+                mPeriodicSamplingInterval,
+                1);
+    }
+
+    private void noteLatencyDelayed() {
+        mLatencyObserverHandler.removeCallbacks(mLatencyObserverRunnable);
+        mLatencyObserverHandler.postDelayed(mLatencyObserverRunnable,
+                mStatsdPushIntervalMinutes * 60 * 1000);
+    }
 
     /** Injector for {@link BinderLatencyObserver}. */
     public static class Injector {
         public Random getRandomGenerator() {
             return new Random();
         }
+
+        public Handler getHandler() {
+            return new Handler(Looper.getMainLooper());
+        }
     }
 
     public BinderLatencyObserver(Injector injector) {
         mRandom = injector.getRandomGenerator();
+        mLatencyObserverHandler = injector.getHandler();
+        mLatencyBuckets = new BinderLatencyBuckets(
+            mBucketCount, mFirstBucketSize, mBucketScaleFactor);
+        noteLatencyDelayed();
     }
 
     /** Should be called when a Binder call completes, will store latency data. */
@@ -65,14 +192,24 @@
         }
 
         LatencyDims dims = new LatencyDims(s.binderClass, s.transactionCode);
-        long callDuration = getElapsedRealtimeMicro() - s.timeStarted;
+        long elapsedTimeMicro = getElapsedRealtimeMicro();
+        long callDuration = elapsedTimeMicro - s.timeStarted;
+
+        // Find the bucket this sample should go to.
+        int bucketIdx = mLatencyBuckets.sampleToBucket(
+                callDuration > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) callDuration);
 
         synchronized (mLock) {
-            if (!mLatencySamples.containsKey(dims)) {
-                mLatencySamples.put(dims, new ArrayList<Long>());
+            int[] buckets = mLatencyHistograms.get(dims);
+            if (buckets == null) {
+                buckets = new int[mBucketCount];
+                mLatencyHistograms.put(dims, buckets);
             }
 
-            mLatencySamples.get(dims).add(callDuration);
+            // Increment the correct bucket.
+            if (buckets[bucketIdx] < Integer.MAX_VALUE) {
+                buckets[bucketIdx] += 1;
+            }
         }
     }
 
@@ -100,11 +237,44 @@
         }
     }
 
+    /** Updates the statsd push interval. */
+    public void setPushInterval(int pushIntervalMinutes) {
+        if (pushIntervalMinutes <= 0) {
+            Slog.w(TAG, "Ignored invalid push interval (value must be positive): "
+                    + pushIntervalMinutes);
+            return;
+        }
+
+        synchronized (mLock) {
+            if (pushIntervalMinutes != mStatsdPushIntervalMinutes) {
+                mStatsdPushIntervalMinutes = pushIntervalMinutes;
+                reset();
+            }
+        }
+    }
+
+    /** Updates the histogram buckets parameters. */
+    public void setHistogramBucketsParams(
+            int bucketCount, int firstBucketSize, float bucketScaleFactor) {
+        synchronized (mLock) {
+            if (bucketCount != mBucketCount || firstBucketSize != mFirstBucketSize
+                    || bucketScaleFactor != mBucketScaleFactor) {
+                mBucketCount = bucketCount;
+                mFirstBucketSize = firstBucketSize;
+                mBucketScaleFactor = bucketScaleFactor;
+                mLatencyBuckets = new BinderLatencyBuckets(
+                    mBucketCount, mFirstBucketSize, mBucketScaleFactor);
+                reset();
+            }
+        }
+    }
+
     /** Resets the sample collection. */
     public void reset() {
         synchronized (mLock) {
-            mLatencySamples.clear();
+            mLatencyHistograms.clear();
         }
+        noteLatencyDelayed();
     }
 
     /** Container for binder latency information. */
@@ -151,7 +321,12 @@
     }
 
     @VisibleForTesting
-    public ArrayMap<LatencyDims, ArrayList<Long>> getLatencySamples() {
-        return mLatencySamples;
+    public ArrayMap<LatencyDims, int[]> getLatencyHistograms() {
+        return mLatencyHistograms;
+    }
+
+    @VisibleForTesting
+    public Runnable getStatsdPushRunnable() {
+        return mLatencyObserverRunnable;
     }
 }
diff --git a/core/jni/OWNERS b/core/jni/OWNERS
index 413242f..07fb729 100644
--- a/core/jni/OWNERS
+++ b/core/jni/OWNERS
@@ -42,6 +42,9 @@
 per-file android_os_HwRemoteBinder* = file:platform/system/libhwbinder:/OWNERS
 per-file EphemeralStorage* = file:platform/system/libhwbinder:/OWNERS
 
+# Sensor
+per-file android_hardware_SensorManager* = arthuri@google.com, bduddie@google.com, stange@google.com
+
 per-file *Zygote* = file:/ZYGOTE_OWNERS
 per-file fd_utils.* = file:/ZYGOTE_OWNERS
 per-file Android.bp = file:platform/build/soong:/OWNERS
diff --git a/core/proto/android/internal/binder_latency.proto b/core/proto/android/internal/binder_latency.proto
new file mode 100644
index 0000000..e32c3e3
--- /dev/null
+++ b/core/proto/android/internal/binder_latency.proto
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+package com.android.internal.os;
+
+option java_outer_classname = "BinderLatencyProto";
+
+/**
+ * RepeatedApiStats proto from atoms.proto, duplicated here so that it's
+ * accessible in the build.
+ * Must be kept in sync with the version in atoms.proto.
+ */
+
+message RepeatedApiStats {
+  repeated ApiStats api_stats = 1;
+}
+
+message Dims {
+  enum ProcessSource {
+    UNKNOWN_PROCESS_SOURCE = 0;
+    SYSTEM_SERVER = 1;
+    TELEPHONY = 2;
+  }
+
+  enum ServiceClassName {
+    UNKNOWN_CLASS = 0;
+  }
+  enum ServiceMethodName {
+    UNKNOWN_METHOD = 0;
+  }
+
+  // Required.
+  optional ProcessSource process_source = 1;
+
+  // The class name of the API making the call to Binder. Enum value
+  // is preferred as uses much less data to store.
+  // This field does not contain PII.
+  oneof service_class {
+    ServiceClassName service_class_name_as_enum = 2;
+    string service_class_name = 3;
+  }
+
+  // Method name of the API call. It can also be a transaction code if we
+  // cannot resolve it to a name. See Binder#getTransactionName. Enum value
+  // is preferred as uses much less data to store.
+  // This field does not contain PII.
+  oneof service_method {
+    ServiceMethodName service_method_name_as_enum = 4;
+    string service_method_name = 5;
+  }
+}
+
+message ApiStats {
+  // required.
+  optional Dims dims = 1;
+
+  // Indicates the first bucket that had any data. Allows omitting any empty
+  // buckets at the start of the bucket list and thus save on data size.
+  optional int32 first_bucket_index = 2;
+  // Stores the count of samples for each bucket. The number of buckets and
+  // their sizes are controlled server side with a flag.
+  repeated int32 buckets = 3;
+}
\ No newline at end of file
diff --git a/core/res/res/values-es-rMX/donottranslate-cldr.xml b/core/res/res/values-es-rMX/donottranslate-cldr.xml
new file mode 100755
index 0000000..db438f2
--- /dev/null
+++ b/core/res/res/values-es-rMX/donottranslate-cldr.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="month_day_year">%-e %B %Y</string>
+    <string name="time_of_day">%H:%M:%S</string>
+    <string name="date_and_time">%-e %b %Y, %H:%M:%S</string>
+    <string name="date_time">%1$s, %2$s</string>
+</resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 89adc8c..fc2634e 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3232,6 +3232,26 @@
   </staging-public-group>
 
   <!-- ===============================================================
+   Resources added in version T of the platform
+
+   NOTE: add <public> elements within a <public-group> like so:
+
+   <public-group type="attr" first-id="0x01010531">
+       <public name="exampleAttr1" />
+       <public name="exampleAttr2" />
+   </public-group>
+
+   To add a new public-group block, choose an id value that is 1 greater
+   than the last of that item above. For example, the last "attr" id
+   value above is 0x01010530, so the public-group of attrs below has
+   the id value of 0x01010531.
+   =============================================================== -->
+  <eat-comment />
+
+  <public-group type="attr" first-id="0x01010640">
+  </public-group>
+
+  <!-- ===============================================================
        DO NOT ADD UN-GROUPED ITEMS HERE
 
        Any new items (attrs, styles, ids, etc.) *must* be added in a
diff --git a/core/tests/coretests/src/android/content/pm/SigningDetailsTest.java b/core/tests/coretests/src/android/content/pm/SigningDetailsTest.java
index 49b720c..cf7e5c66 100644
--- a/core/tests/coretests/src/android/content/pm/SigningDetailsTest.java
+++ b/core/tests/coretests/src/android/content/pm/SigningDetailsTest.java
@@ -15,19 +15,18 @@
  */
 package android.content.pm;
 
-import static android.content.pm.PackageParser.SigningDetails.CertCapabilities.AUTH;
-import static android.content.pm.PackageParser.SigningDetails.CertCapabilities.INSTALLED_DATA;
-import static android.content.pm.PackageParser.SigningDetails.CertCapabilities.PERMISSION;
-import static android.content.pm.PackageParser.SigningDetails.CertCapabilities.ROLLBACK;
-import static android.content.pm.PackageParser.SigningDetails.CertCapabilities.SHARED_USER_ID;
-import static android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3;
+import static android.content.pm.SigningDetails.CertCapabilities.AUTH;
+import static android.content.pm.SigningDetails.CertCapabilities.INSTALLED_DATA;
+import static android.content.pm.SigningDetails.CertCapabilities.PERMISSION;
+import static android.content.pm.SigningDetails.CertCapabilities.ROLLBACK;
+import static android.content.pm.SigningDetails.CertCapabilities.SHARED_USER_ID;
+import static android.content.pm.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
-import android.content.pm.PackageParser.SigningDetails;
 import android.util.ArraySet;
 import android.util.PackageUtils;
 
@@ -990,11 +989,11 @@
     private void assertSigningDetailsContainsLineage(SigningDetails details,
             String... pastSigners) {
         // This method should only be invoked for results that contain a single signer.
-        assertEquals(1, details.signatures.length);
-        assertTrue(details.signatures[0].toCharsString().equalsIgnoreCase(
+        assertEquals(1, details.getSignatures().length);
+        assertTrue(details.getSignatures()[0].toCharsString().equalsIgnoreCase(
                 pastSigners[pastSigners.length - 1]));
         Set<String> signatures = new ArraySet<>(pastSigners);
-        for (Signature pastSignature : details.pastSigningCertificates) {
+        for (Signature pastSignature : details.getPastSigningCertificates()) {
             assertTrue(signatures.remove(pastSignature.toCharsString()));
         }
         assertEquals(0, signatures.size());
diff --git a/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java b/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
index 5334a45..7890168 100644
--- a/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
@@ -934,7 +934,7 @@
         bcs.elapsedTime += 20;
         bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID);
 
-        assertEquals(1, bcs.getLatencyObserver().getLatencySamples().size());
+        assertEquals(1, bcs.getLatencyObserver().getLatencyHistograms().size());
     }
 
     @Test
@@ -948,7 +948,7 @@
         bcs.elapsedTime += 20;
         bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID);
 
-        assertEquals(0, bcs.getLatencyObserver().getLatencySamples().size());
+        assertEquals(0, bcs.getLatencyObserver().getLatencyHistograms().size());
     }
 
     private static class TestHandler extends Handler {
diff --git a/core/tests/coretests/src/com/android/internal/os/BinderLatencyBucketsTest.java b/core/tests/coretests/src/com/android/internal/os/BinderLatencyBucketsTest.java
new file mode 100644
index 0000000..b2054f1
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/BinderLatencyBucketsTest.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.os;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertEquals;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+@Presubmit
+public class BinderLatencyBucketsTest {
+    @Test
+    public void testBucketThresholds() {
+        BinderLatencyBuckets latencyBuckets = new BinderLatencyBuckets(10, 2, 1.45f);
+        assertThat(latencyBuckets.getBuckets())
+            .asList()
+            .containsExactly(2, 3, 4, 6, 8, 12, 18, 26, 39)
+            .inOrder();
+    }
+
+    @Test
+    public void testSampleAssignment() {
+        BinderLatencyBuckets latencyBuckets = new BinderLatencyBuckets(10, 2, 1.45f);
+        assertEquals(0, latencyBuckets.sampleToBucket(0));
+        assertEquals(0, latencyBuckets.sampleToBucket(1));
+        assertEquals(1, latencyBuckets.sampleToBucket(2));
+        assertEquals(2, latencyBuckets.sampleToBucket(3));
+        assertEquals(3, latencyBuckets.sampleToBucket(4));
+        assertEquals(5, latencyBuckets.sampleToBucket(9));
+        assertEquals(6, latencyBuckets.sampleToBucket(13));
+        assertEquals(7, latencyBuckets.sampleToBucket(25));
+        assertEquals(9, latencyBuckets.sampleToBucket(100));
+    }
+
+    @Test
+    public void testMaxIntBuckets() {
+        BinderLatencyBuckets latencyBuckets = new BinderLatencyBuckets(5, Integer.MAX_VALUE / 2, 2);
+        assertThat(latencyBuckets.getBuckets())
+            .asList()
+            .containsExactly(Integer.MAX_VALUE / 2, Integer.MAX_VALUE - 1)
+            .inOrder();
+
+        assertEquals(0, latencyBuckets.sampleToBucket(0));
+        assertEquals(0, latencyBuckets.sampleToBucket(Integer.MAX_VALUE / 2 - 1));
+        assertEquals(1, latencyBuckets.sampleToBucket(Integer.MAX_VALUE - 2));
+        assertEquals(2, latencyBuckets.sampleToBucket(Integer.MAX_VALUE));
+    }
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/BinderLatencyObserverTest.java b/core/tests/coretests/src/com/android/internal/os/BinderLatencyObserverTest.java
index 36915a2..bf87683 100644
--- a/core/tests/coretests/src/com/android/internal/os/BinderLatencyObserverTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BinderLatencyObserverTest.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.os;
 
+import static com.android.internal.os.BinderLatencyProto.Dims.SYSTEM_SERVER;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertEquals;
@@ -23,12 +25,16 @@
 import android.os.Binder;
 import android.platform.test.annotations.Presubmit;
 import android.util.ArrayMap;
+import android.util.proto.ProtoOutputStream;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.os.BinderInternal.CallSession;
 import com.android.internal.os.BinderLatencyObserver.LatencyDims;
+import com.android.internal.os.BinderLatencyProto.ApiStats;
+import com.android.internal.os.BinderLatencyProto.Dims;
+import com.android.internal.os.BinderLatencyProto.RepeatedApiStats;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -44,47 +50,199 @@
     @Test
     public void testLatencyCollectionWithMultipleClasses() {
         TestBinderLatencyObserver blo = new TestBinderLatencyObserver();
+        blo.setHistogramBucketsParams(5, 5, 1.125f);
 
         Binder binder = new Binder();
         CallSession callSession = new CallSession();
         callSession.binderClass = binder.getClass();
         callSession.transactionCode = 1;
+
+        blo.setElapsedTime(2);
         blo.callEnded(callSession);
+        blo.setElapsedTime(4);
+        blo.callEnded(callSession);
+        blo.setElapsedTime(6);
         blo.callEnded(callSession);
         callSession.transactionCode = 2;
+        blo.setElapsedTime(8);
+        blo.callEnded(callSession);
+        blo.setElapsedTime(10);
         blo.callEnded(callSession);
 
-        ArrayMap<LatencyDims, ArrayList<Long>> latencySamples = blo.getLatencySamples();
-        assertEquals(2, latencySamples.keySet().size());
-        assertThat(latencySamples.get(new LatencyDims(binder.getClass(), 1)))
-            .containsExactlyElementsIn(Arrays.asList(1L, 2L));
-        assertThat(latencySamples.get(new LatencyDims(binder.getClass(), 2))).containsExactly(3L);
+        ArrayMap<LatencyDims, int[]> latencyHistograms = blo.getLatencyHistograms();
+        assertEquals(2, latencyHistograms.keySet().size());
+        assertThat(latencyHistograms.get(new LatencyDims(binder.getClass(), 1)))
+            .asList().containsExactly(2, 0, 1, 0, 0).inOrder();
+        assertThat(latencyHistograms.get(new LatencyDims(binder.getClass(), 2)))
+            .asList().containsExactly(0, 0, 0, 0, 2).inOrder();
     }
 
     @Test
     public void testSampling() {
         TestBinderLatencyObserver blo = new TestBinderLatencyObserver();
         blo.setSamplingInterval(2);
+        blo.setHistogramBucketsParams(5, 5, 1.125f);
 
         Binder binder = new Binder();
         CallSession callSession = new CallSession();
         callSession.binderClass = binder.getClass();
         callSession.transactionCode = 1;
+        blo.setElapsedTime(2);
         blo.callEnded(callSession);
         callSession.transactionCode = 2;
+        blo.setElapsedTime(4);
         blo.callEnded(callSession);
 
-        ArrayMap<LatencyDims, ArrayList<Long>> latencySamples = blo.getLatencySamples();
-        assertEquals(1, latencySamples.size());
-        LatencyDims dims = latencySamples.keySet().iterator().next();
+        ArrayMap<LatencyDims, int[]> latencyHistograms = blo.getLatencyHistograms();
+        assertEquals(1, latencyHistograms.size());
+        LatencyDims dims = latencyHistograms.keySet().iterator().next();
         assertEquals(binder.getClass(), dims.getBinderClass());
         assertEquals(1, dims.getTransactionCode());
-        ArrayList<Long> values = latencySamples.get(dims);
-        assertThat(values).containsExactly(1L);
+        assertThat(latencyHistograms.get(dims)).asList().containsExactly(1, 0, 0, 0, 0).inOrder();
+    }
+
+    @Test
+    public void testTooCallLengthOverflow() {
+        TestBinderLatencyObserver blo = new TestBinderLatencyObserver();
+        blo.setHistogramBucketsParams(5, 5, 1.125f);
+
+        Binder binder = new Binder();
+        CallSession callSession = new CallSession();
+        callSession.binderClass = binder.getClass();
+        callSession.transactionCode = 1;
+        blo.setElapsedTime(2L + (long) Integer.MAX_VALUE);
+        blo.callEnded(callSession);
+
+        // The long call should be capped to maxint (to not overflow) and placed in the last bucket.
+        assertThat(blo.getLatencyHistograms()
+            .get(new LatencyDims(binder.getClass(), 1)))
+            .asList().containsExactly(0, 0, 0, 0, 1)
+            .inOrder();
+    }
+
+    @Test
+    public void testHistogramBucketOverflow() {
+        TestBinderLatencyObserver blo = new TestBinderLatencyObserver();
+        blo.setHistogramBucketsParams(3, 5, 1.125f);
+
+        Binder binder = new Binder();
+        CallSession callSession = new CallSession();
+        callSession.binderClass = binder.getClass();
+        callSession.transactionCode = 1;
+        blo.setElapsedTime(2);
+        blo.callEnded(callSession);
+
+        LatencyDims dims = new LatencyDims(binder.getClass(), 1);
+        // Fill the buckets with maxint.
+        Arrays.fill(blo.getLatencyHistograms().get(dims), Integer.MAX_VALUE);
+        assertThat(blo.getLatencyHistograms().get(dims))
+            .asList().containsExactly(Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE);
+        // Try to add another sample.
+        blo.setElapsedTime(2);
+        blo.callEnded(callSession);
+        // Make sure the buckets don't overflow.
+        assertThat(blo.getLatencyHistograms().get(dims))
+            .asList().containsExactly(Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE);
+    }
+
+    @Test
+    public void testSingleAtomPush() {
+        TestBinderLatencyObserver blo = new TestBinderLatencyObserver();
+
+        Binder binder = new Binder();
+        CallSession callSession = new CallSession();
+        callSession.binderClass = binder.getClass();
+        callSession.transactionCode = 1;
+        blo.setElapsedTime(7);
+        blo.callEnded(callSession);
+        blo.callEnded(callSession);
+        blo.setElapsedTime(8);
+        blo.callEnded(callSession);
+
+        // Trigger the statsd push.
+        blo.getStatsdPushRunnable().run();
+
+        ProtoOutputStream expectedProto = new ProtoOutputStream();
+        long apiStatsToken = expectedProto.start(RepeatedApiStats.API_STATS);
+        long dimsToken = expectedProto.start(ApiStats.DIMS);
+        expectedProto.write(Dims.PROCESS_SOURCE, SYSTEM_SERVER);
+        expectedProto.write(Dims.SERVICE_CLASS_NAME, binder.getClass().getName());
+        expectedProto.write(Dims.SERVICE_METHOD_NAME, "1");
+        expectedProto.end(dimsToken);
+        expectedProto.write(ApiStats.FIRST_BUCKET_INDEX, 3);
+        expectedProto.write(ApiStats.BUCKETS, 2);
+        expectedProto.write(ApiStats.BUCKETS, 1);
+        expectedProto.end(apiStatsToken);
+
+        assertThat(blo.getWrittenAtoms())
+                .containsExactly(Arrays.toString(expectedProto.getBytes()));
+    }
+
+    @Test
+    public void testMultipleAtomPush() {
+        TestBinderLatencyObserver blo = new TestBinderLatencyObserver();
+
+        BinderTransactionNameResolver resolver = new BinderTransactionNameResolver();
+
+
+        Binder binder = new Binder();
+        CallSession callSession = new CallSession();
+        callSession.binderClass = binder.getClass();
+        callSession.transactionCode = 1;
+        blo.setElapsedTime(1);
+        blo.callEnded(callSession);
+        callSession.transactionCode = 2;
+        blo.setElapsedTime(5);
+        blo.callEnded(callSession);
+        callSession.transactionCode = 3;
+        blo.callEnded(callSession);
+
+        // Trigger the statsd push.
+        blo.getStatsdPushRunnable().run();
+
+        ProtoOutputStream expectedProto1 = new ProtoOutputStream();
+        long apiStatsToken = expectedProto1.start(RepeatedApiStats.API_STATS);
+        long dimsToken = expectedProto1.start(ApiStats.DIMS);
+        expectedProto1.write(Dims.PROCESS_SOURCE, SYSTEM_SERVER);
+        expectedProto1.write(Dims.SERVICE_CLASS_NAME, binder.getClass().getName());
+        expectedProto1.write(Dims.SERVICE_METHOD_NAME, "1");
+        expectedProto1.end(dimsToken);
+        expectedProto1.write(ApiStats.FIRST_BUCKET_INDEX, 0);
+        expectedProto1.write(ApiStats.BUCKETS, 1);
+        expectedProto1.end(apiStatsToken);
+
+        apiStatsToken = expectedProto1.start(RepeatedApiStats.API_STATS);
+        dimsToken = expectedProto1.start(ApiStats.DIMS);
+        expectedProto1.write(Dims.PROCESS_SOURCE, SYSTEM_SERVER);
+        expectedProto1.write(Dims.SERVICE_CLASS_NAME, binder.getClass().getName());
+        expectedProto1.write(Dims.SERVICE_METHOD_NAME, "2");
+        expectedProto1.end(dimsToken);
+        expectedProto1.write(ApiStats.FIRST_BUCKET_INDEX, 1);
+        expectedProto1.write(ApiStats.BUCKETS, 1);
+        expectedProto1.end(apiStatsToken);
+
+        ProtoOutputStream expectedProto2 = new ProtoOutputStream();
+        apiStatsToken = expectedProto2.start(RepeatedApiStats.API_STATS);
+        dimsToken = expectedProto2.start(ApiStats.DIMS);
+        expectedProto2.write(Dims.PROCESS_SOURCE, SYSTEM_SERVER);
+        expectedProto2.write(Dims.SERVICE_CLASS_NAME, binder.getClass().getName());
+        expectedProto2.write(Dims.SERVICE_METHOD_NAME, "3");
+        expectedProto2.end(dimsToken);
+        expectedProto2.write(ApiStats.FIRST_BUCKET_INDEX, 1);
+        expectedProto2.write(ApiStats.BUCKETS, 1);
+        expectedProto2.end(apiStatsToken);
+
+        // Each ApiStats is around ~60 bytes so only two should fit in an atom.
+        assertThat(blo.getWrittenAtoms())
+                .containsExactly(
+                        Arrays.toString(expectedProto1.getBytes()),
+                        Arrays.toString(expectedProto2.getBytes()))
+                .inOrder();
     }
 
     public static class TestBinderLatencyObserver extends BinderLatencyObserver {
-        private long mElapsedTimeCallCount = 0;
+        private long mElapsedTime = 0;
+        private ArrayList<String> mWrittenAtoms;
 
         TestBinderLatencyObserver() {
             // Make random generator not random.
@@ -100,11 +258,30 @@
                 }
             });
             setSamplingInterval(1);
+            mWrittenAtoms = new ArrayList<>();
         }
 
         @Override
         protected long getElapsedRealtimeMicro() {
-            return ++mElapsedTimeCallCount;
+            return mElapsedTime;
+        }
+
+        @Override
+        protected int getMaxAtomSizeBytes() {
+            return 1100;
+        }
+
+        @Override
+        protected void writeAtomToStatsd(ProtoOutputStream atom) {
+            mWrittenAtoms.add(Arrays.toString(atom.getBytes()));
+        }
+
+        public void setElapsedTime(long time) {
+            mElapsedTime = time;
+        }
+
+        public ArrayList<String> getWrittenAtoms() {
+            return mWrittenAtoms;
         }
     }
 }
diff --git a/data/etc/car/com.android.car.activityresolver.xml b/data/etc/car/com.android.car.activityresolver.xml
index d48bc15..927c738 100644
--- a/data/etc/car/com.android.car.activityresolver.xml
+++ b/data/etc/car/com.android.car.activityresolver.xml
@@ -19,3 +19,4 @@
         <permission name="android.permission.MANAGE_USERS"/>
     </privapp-permissions>
 </permissions>
+
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 2fe8b28..c374320 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -371,6 +371,8 @@
         <!-- Needed for test only -->
         <permission name="android.permission.PACKET_KEEPALIVE_OFFLOAD" />
         <permission name="android.permission.POWER_SAVER" />
+        <!-- Needed for CTS tests -->
+        <permission name="android.permission.READ_ACTIVE_EMERGENCY_SESSION"/>
         <permission name="android.permission.READ_CARRIER_APP_INFO"/>
         <permission name="android.permission.READ_FRAME_BUFFER"/>
         <permission name="android.permission.READ_LOWPAN_CREDENTIAL"/>
@@ -547,6 +549,11 @@
         <permission name="android.permission.CONTROL_VPN"/>
     </privapp-permissions>
 
+    <privapp-permissions package="com.android.wallpaper.livepicker">
+        <permission name="android.permission.SET_WALLPAPER_COMPONENT"/>
+        <permission name="android.permission.BIND_WALLPAPER"/>
+    </privapp-permissions>
+
     <privapp-permissions package="com.android.dynsystem">
         <permission name="android.permission.REBOOT"/>
         <permission name="android.permission.MANAGE_DYNAMIC_SYSTEM"/>
diff --git a/errorprone/java/com/google/errorprone/bugpatterns/android/UnattributedNoteOpCallChecker.java b/errorprone/java/com/google/errorprone/bugpatterns/android/UnattributedNoteOpCallChecker.java
new file mode 100644
index 0000000..d39978f5
--- /dev/null
+++ b/errorprone/java/com/google/errorprone/bugpatterns/android/UnattributedNoteOpCallChecker.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.errorprone.bugpatterns.android;
+
+import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
+import static com.google.errorprone.matchers.Matchers.instanceMethod;
+import static com.google.errorprone.matchers.Matchers.methodInvocation;
+
+import com.google.auto.service.AutoService;
+import com.google.errorprone.BugPattern;
+import com.google.errorprone.VisitorState;
+import com.google.errorprone.bugpatterns.BugChecker;
+import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
+import com.google.errorprone.matchers.Description;
+import com.google.errorprone.matchers.Matcher;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.MethodInvocationTree;
+
+@AutoService(BugChecker.class)
+@BugPattern(
+    name = "AndroidFrameworkUnattributedNoteOpCall",
+    summary = "Verifies that a noteOp() call is attributed",
+    severity = WARNING)
+public final class UnattributedNoteOpCallChecker extends BugChecker
+        implements MethodInvocationTreeMatcher {
+
+    private static final Matcher<ExpressionTree> UNATTRIBUTED_NOTEOP_CALL_1 = methodInvocation(
+            instanceMethod().onExactClass("android.app.AppOpsManager")
+                    .withSignature("noteOp(int,int,java.lang.String)"));
+    private static final Matcher<ExpressionTree> UNATTRIBUTED_NOTEOP_CALL_2 = methodInvocation(
+            instanceMethod().onExactClass("android.app.AppOpsManager")
+                    .withSignature("noteOp(java.lang.String,int,java.lang.String)"));
+    private static final Matcher<ExpressionTree> UNATTRIBUTED_NOTEOP_CALL_3 = methodInvocation(
+            instanceMethod().onExactClass("android.app.AppOpsManager")
+                    .withSignature("noteOp(int)"));
+
+    @Override
+    public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
+        if (UNATTRIBUTED_NOTEOP_CALL_1.matches(tree, state)
+            || UNATTRIBUTED_NOTEOP_CALL_2.matches(tree, state)
+            || UNATTRIBUTED_NOTEOP_CALL_3.matches(tree, state)) {
+            return buildDescription(tree)
+                .setMessage("Unattributed noteOp call! Please use noteOp(int, String, String, String) or noteOp(int, CallerIdentity)")
+                .build();
+        }
+        return Description.NO_MATCH;
+    }
+}
diff --git a/errorprone/tests/java/com/google/errorprone/bugpatterns/android/UnattributedNoteOpCallCheckerTest.java b/errorprone/tests/java/com/google/errorprone/bugpatterns/android/UnattributedNoteOpCallCheckerTest.java
new file mode 100644
index 0000000..4a559c2
--- /dev/null
+++ b/errorprone/tests/java/com/google/errorprone/bugpatterns/android/UnattributedNoteOpCallCheckerTest.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.errorprone.bugpatterns.android;
+
+import com.google.errorprone.CompilationTestHelper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class UnattributedNoteOpCallCheckerTest {
+    private CompilationTestHelper mCompilationHelper;
+
+    @Before
+    public void setUp() {
+        mCompilationHelper = CompilationTestHelper.newInstance(
+                UnattributedNoteOpCallChecker.class, getClass());
+    }
+
+    @Test
+    public void testNoteOp() {
+        mCompilationHelper
+                .addSourceFile("/android/app/AppOpsManager.java")
+                .addSourceLines("Example.java",
+                        "import android.app.AppOpsManager;",
+                        "public class Example {",
+                        "  void example() {",
+                        "    AppOpsManager mAppOps = new AppOpsManager();",
+                        "    mAppOps.noteOp(\"foo\", 0, \"bar\", \"baz\", \"qux\");",
+                        "    mAppOps.noteOp(0, 0, \"bar\", \"baz\", \"qux\");",
+                        "    // BUG: Diagnostic contains:",
+                        "    mAppOps.noteOp(1, 2, \"foo\");",
+                        "    // BUG: Diagnostic contains:",
+                        "    mAppOps.noteOp(\"foo\", 1, \"bar\");",
+                        "    // BUG: Diagnostic contains:",
+                        "    mAppOps.noteOp(1);",
+                        "  }",
+                        "}")
+                .doTest();
+    }
+}
diff --git a/errorprone/tests/res/android/app/AppOpsManager.java b/errorprone/tests/res/android/app/AppOpsManager.java
new file mode 100644
index 0000000..cccdfd8
--- /dev/null
+++ b/errorprone/tests/res/android/app/AppOpsManager.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+public class AppOpsManager {
+
+    public int noteOp(String op, int uid, String packageName) {
+        throw new UnsupportedOperationException();
+    }
+
+    public int noteOp(int op) {
+        throw new UnsupportedOperationException();
+    }
+
+    public int noteOp(int op, int uid, String packageName) {
+        throw new UnsupportedOperationException();
+    }
+
+    public int noteOp(String op, int uid, String packageName,
+            String attributionTag, String message) {
+        throw new UnsupportedOperationException();
+    }
+
+    public int noteOp(int op, int uid, String packageName,
+            String attributionTag, String message) {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/libs/WindowManager/Shell/res/values-hi/strings.xml b/libs/WindowManager/Shell/res/values-hi/strings.xml
index a969386..b2c0055 100644
--- a/libs/WindowManager/Shell/res/values-hi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hi/strings.xml
@@ -67,7 +67,7 @@
     <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"हाल ही के बबल्स मौजूद नहीं हैं"</string>
     <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"हाल ही के बबल्स और हटाए गए बबल्स यहां दिखेंगे"</string>
     <string name="notification_bubble_title" msgid="6082910224488253378">"बबल"</string>
-    <string name="manage_bubbles_text" msgid="7730624269650594419">"प्रबंधित करें"</string>
+    <string name="manage_bubbles_text" msgid="7730624269650594419">"मैनेज करें"</string>
     <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"बबल खारिज किया गया."</string>
     <string name="restart_button_description" msgid="5887656107651190519">"इस ऐप्लिकेशन को रीस्टार्ट करने और फ़ुल स्क्रीन पर देखने के लिए टैप करें."</string>
     <string name="got_it" msgid="4428750913636945527">"ठीक है"</string>
diff --git a/libs/WindowManager/Shell/res/values-iw/strings_tv.xml b/libs/WindowManager/Shell/res/values-iw/strings_tv.xml
index 8ca54e0..ef98a9c 100644
--- a/libs/WindowManager/Shell/res/values-iw/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-iw/strings_tv.xml
@@ -19,6 +19,6 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="notification_channel_tv_pip" msgid="2576686079160402435">"תמונה בתוך תמונה"</string>
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(תוכנית ללא כותרת)"</string>
-    <string name="pip_close" msgid="9135220303720555525">"‏סגור PIP"</string>
+    <string name="pip_close" msgid="9135220303720555525">"‏סגירת PIP"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"מסך מלא"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-ne/strings.xml b/libs/WindowManager/Shell/res/values-ne/strings.xml
index 882ac37..dfa364a 100644
--- a/libs/WindowManager/Shell/res/values-ne/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ne/strings.xml
@@ -30,7 +30,7 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"आकार बदल्नुहोस्"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"एप विभाजित स्क्रिनमा काम नगर्न सक्छ।"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"अनुप्रयोगले विभाजित-स्क्रिनलाई समर्थन गर्दैन।"</string>
-    <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"यो अनुप्रयोगले सहायक प्रदर्शनमा काम नगर्नसक्छ।"</string>
+    <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"यो एपले सहायक प्रदर्शनमा काम नगर्नसक्छ।"</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"अनुप्रयोगले सहायक प्रदर्शनहरूमा लञ्च सुविधालाई समर्थन गर्दैन।"</string>
     <string name="accessibility_divider" msgid="703810061635792791">"विभाजित-स्क्रिन छुट्याउने"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"बायाँ भाग फुल स्क्रिन"</string>
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 19098fd..bb975a4 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
@@ -343,7 +343,6 @@
                     mOneHandedAccessibilityUtil.getOneHandedStartDescription());
             mDisplayAreaOrganizer.scheduleOffset(0, yOffSet);
             mTimeoutHandler.resetTimer();
-
             mOneHandedUiEventLogger.writeEvent(
                     OneHandedUiEventLogger.EVENT_ONE_HANDED_TRIGGER_GESTURE_IN);
         }
@@ -356,6 +355,9 @@
                     mOneHandedAccessibilityUtil.getOneHandedStopDescription());
             mDisplayAreaOrganizer.scheduleOffset(0, 0);
             mTimeoutHandler.removeTimer();
+            //  Log metrics for Gesture navigation mode.
+            mOneHandedUiEventLogger.writeEvent(
+                    OneHandedUiEventLogger.EVENT_ONE_HANDED_TRIGGER_GESTURE_OUT);
         }
     }
 
diff --git a/media/Android.bp b/media/Android.bp
index a66236e..cf4a0b1 100644
--- a/media/Android.bp
+++ b/media/Android.bp
@@ -8,23 +8,6 @@
 }
 
 aidl_interface {
-    name: "audio_common-aidl",
-    unstable: true,
-    host_supported: true,
-    vendor_available: true,
-    local_include_dir: "aidl",
-    double_loadable: true,
-    srcs: [
-        "aidl/android/media/audio/common/AudioChannelMask.aidl",
-        "aidl/android/media/audio/common/AudioConfig.aidl",
-        "aidl/android/media/audio/common/AudioFormat.aidl",
-        "aidl/android/media/audio/common/AudioOffloadInfo.aidl",
-        "aidl/android/media/audio/common/AudioStreamType.aidl",
-        "aidl/android/media/audio/common/AudioUsage.aidl",
-    ],
-}
-
-aidl_interface {
     name: "media_permission-aidl",
     unstable: true,
     host_supported: true,
@@ -40,30 +23,92 @@
     name: "soundtrigger_middleware-aidl",
     unstable: true,
     local_include_dir: "aidl",
+    backend: {
+        java: {
+            sdk_version: "module_current",
+        },
+    },
     srcs: [
-        "aidl/android/media/soundtrigger_middleware/AudioCapabilities.aidl",
-        "aidl/android/media/soundtrigger_middleware/ConfidenceLevel.aidl",
         "aidl/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl",
         "aidl/android/media/soundtrigger_middleware/ISoundTriggerMiddlewareService.aidl",
         "aidl/android/media/soundtrigger_middleware/ISoundTriggerModule.aidl",
-        "aidl/android/media/soundtrigger_middleware/ModelParameter.aidl",
-        "aidl/android/media/soundtrigger_middleware/ModelParameterRange.aidl",
-        "aidl/android/media/soundtrigger_middleware/Phrase.aidl",
-        "aidl/android/media/soundtrigger_middleware/PhraseRecognitionEvent.aidl",
-        "aidl/android/media/soundtrigger_middleware/PhraseRecognitionExtra.aidl",
-        "aidl/android/media/soundtrigger_middleware/PhraseSoundModel.aidl",
-        "aidl/android/media/soundtrigger_middleware/RecognitionConfig.aidl",
-        "aidl/android/media/soundtrigger_middleware/RecognitionEvent.aidl",
-        "aidl/android/media/soundtrigger_middleware/RecognitionMode.aidl",
-        "aidl/android/media/soundtrigger_middleware/RecognitionStatus.aidl",
-        "aidl/android/media/soundtrigger_middleware/SoundModel.aidl",
-        "aidl/android/media/soundtrigger_middleware/SoundModelType.aidl",
         "aidl/android/media/soundtrigger_middleware/SoundTriggerModuleDescriptor.aidl",
-        "aidl/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl",
-        "aidl/android/media/soundtrigger_middleware/Status.aidl",
     ],
     imports: [
-        "audio_common-aidl",
+        "android.media.soundtrigger.types",
         "media_permission-aidl",
     ],
 }
+
+aidl_interface {
+    name: "android.media.audio.common.types",
+    vendor_available: true,
+    host_supported: true,
+    double_loadable: true,
+    flags: ["-Werror", "-Weverything", ],
+    local_include_dir: "aidl",
+    srcs: [
+        "aidl/android/media/audio/common/AudioChannelMask.aidl",
+        "aidl/android/media/audio/common/AudioConfig.aidl",
+        "aidl/android/media/audio/common/AudioFormat.aidl",
+        "aidl/android/media/audio/common/AudioOffloadInfo.aidl",
+        "aidl/android/media/audio/common/AudioStreamType.aidl",
+        "aidl/android/media/audio/common/AudioUsage.aidl",
+    ],
+    stability: "vintf",
+    backend: {
+        cpp: {
+            enabled: true,
+        },
+        java: {
+            sdk_version: "module_current",
+        },
+        ndk: {
+            vndk: {
+                enabled: true,
+            },
+        },
+    },
+}
+
+aidl_interface {
+    name: "android.media.soundtrigger.types",
+    vendor_available: true,
+    flags: ["-Werror", "-Weverything", ],
+    local_include_dir: "aidl",
+    srcs: [
+        "aidl/android/media/soundtrigger/AudioCapabilities.aidl",
+        "aidl/android/media/soundtrigger/ConfidenceLevel.aidl",
+        "aidl/android/media/soundtrigger/ModelParameter.aidl",
+        "aidl/android/media/soundtrigger/ModelParameterRange.aidl",
+        "aidl/android/media/soundtrigger/Phrase.aidl",
+        "aidl/android/media/soundtrigger/PhraseRecognitionEvent.aidl",
+        "aidl/android/media/soundtrigger/PhraseRecognitionExtra.aidl",
+        "aidl/android/media/soundtrigger/PhraseSoundModel.aidl",
+        "aidl/android/media/soundtrigger/Properties.aidl",
+        "aidl/android/media/soundtrigger/RecognitionConfig.aidl",
+        "aidl/android/media/soundtrigger/RecognitionEvent.aidl",
+        "aidl/android/media/soundtrigger/RecognitionMode.aidl",
+        "aidl/android/media/soundtrigger/RecognitionStatus.aidl",
+        "aidl/android/media/soundtrigger/SoundModel.aidl",
+        "aidl/android/media/soundtrigger/SoundModelType.aidl",
+        "aidl/android/media/soundtrigger/Status.aidl",
+    ],
+    stability: "vintf",
+    backend: {
+        cpp: {
+            enabled: true,
+        },
+        java: {
+            sdk_version: "module_current",
+        },
+        ndk: {
+            vndk: {
+                enabled: true,
+            },
+        },
+    },
+    imports: [
+        "android.media.audio.common.types",
+    ],
+}
diff --git a/media/aidl/android/media/audio/common/AudioChannelMask.aidl b/media/aidl/android/media/audio/common/AudioChannelMask.aidl
index b9b08e6..17be8dd 100644
--- a/media/aidl/android/media/audio/common/AudioChannelMask.aidl
+++ b/media/aidl/android/media/audio/common/AudioChannelMask.aidl
@@ -57,8 +57,11 @@
  * checking the channel mask, the implementer should look for ways to fix it
  * with additional information outside of the mask.
  *
+ * TODO: this should be replaced with strings or other mechanisms that are easy to extend, in line
+ *     with the audio HAL conventions.
  * {@hide}
  */
+@VintfStability
 @Backing(type="int")
 enum AudioChannelMask {
     /**
@@ -113,42 +116,43 @@
      */
     OUT_HAPTIC_A = 0x20000000,
     OUT_HAPTIC_B = 0x10000000,
-// TODO(ytai): Aliases not currently supported in AIDL - can inline the values.
-//    OUT_MONO = OUT_FRONT_LEFT,
-//    OUT_STEREO = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT),
-//    OUT_2POINT1 = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_LOW_FREQUENCY),
-//    OUT_2POINT0POINT2 = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_TOP_SIDE_LEFT | OUT_TOP_SIDE_RIGHT),
-//    OUT_2POINT1POINT2 = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_TOP_SIDE_LEFT | OUT_TOP_SIDE_RIGHT | OUT_LOW_FREQUENCY),
-//    OUT_3POINT0POINT2 = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_FRONT_CENTER | OUT_TOP_SIDE_LEFT | OUT_TOP_SIDE_RIGHT),
-//    OUT_3POINT1POINT2 = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_FRONT_CENTER | OUT_TOP_SIDE_LEFT | OUT_TOP_SIDE_RIGHT | OUT_LOW_FREQUENCY),
-//    OUT_QUAD = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_BACK_LEFT | OUT_BACK_RIGHT),
-//    OUT_QUAD_BACK = OUT_QUAD,
-//    /**
-//     * like OUT_QUAD_BACK with *_SIDE_* instead of *_BACK_*
-//     */
-//    OUT_QUAD_SIDE = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_SIDE_LEFT | OUT_SIDE_RIGHT),
-//    OUT_SURROUND = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_FRONT_CENTER | OUT_BACK_CENTER),
-//    OUT_PENTA = (OUT_QUAD | OUT_FRONT_CENTER),
-//    OUT_5POINT1 = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_FRONT_CENTER | OUT_LOW_FREQUENCY | OUT_BACK_LEFT | OUT_BACK_RIGHT),
-//    OUT_5POINT1_BACK = OUT_5POINT1,
-//    /**
-//     * like OUT_5POINT1_BACK with *_SIDE_* instead of *_BACK_*
-//     */
-//    OUT_5POINT1_SIDE = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_FRONT_CENTER | OUT_LOW_FREQUENCY | OUT_SIDE_LEFT | OUT_SIDE_RIGHT),
-//    OUT_5POINT1POINT2 = (OUT_5POINT1 | OUT_TOP_SIDE_LEFT | OUT_TOP_SIDE_RIGHT),
-//    OUT_5POINT1POINT4 = (OUT_5POINT1 | OUT_TOP_FRONT_LEFT | OUT_TOP_FRONT_RIGHT | OUT_TOP_BACK_LEFT | OUT_TOP_BACK_RIGHT),
-//    OUT_6POINT1 = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_FRONT_CENTER | OUT_LOW_FREQUENCY | OUT_BACK_LEFT | OUT_BACK_RIGHT | OUT_BACK_CENTER),
-//    /**
-//     * matches the correct AudioFormat.CHANNEL_OUT_7POINT1_SURROUND
-//     */
-//    OUT_7POINT1 = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_FRONT_CENTER | OUT_LOW_FREQUENCY | OUT_BACK_LEFT | OUT_BACK_RIGHT | OUT_SIDE_LEFT | OUT_SIDE_RIGHT),
-//    OUT_7POINT1POINT2 = (OUT_7POINT1 | OUT_TOP_SIDE_LEFT | OUT_TOP_SIDE_RIGHT),
-//    OUT_7POINT1POINT4 = (OUT_7POINT1 | OUT_TOP_FRONT_LEFT | OUT_TOP_FRONT_RIGHT | OUT_TOP_BACK_LEFT | OUT_TOP_BACK_RIGHT),
-//    OUT_MONO_HAPTIC_A = (OUT_FRONT_LEFT | OUT_HAPTIC_A),
-//    OUT_STEREO_HAPTIC_A = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_HAPTIC_A),
-//    OUT_HAPTIC_AB = (OUT_HAPTIC_A | OUT_HAPTIC_B),
-//    OUT_MONO_HAPTIC_AB = (OUT_FRONT_LEFT | OUT_HAPTIC_A | OUT_HAPTIC_B),
-//    OUT_STEREO_HAPTIC_AB = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_HAPTIC_A | OUT_HAPTIC_B),
+
+    OUT_MONO = OUT_FRONT_LEFT,
+    OUT_STEREO = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT),
+    OUT_2POINT1 = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_LOW_FREQUENCY),
+    OUT_2POINT0POINT2 = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_TOP_SIDE_LEFT | OUT_TOP_SIDE_RIGHT),
+    OUT_2POINT1POINT2 = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_TOP_SIDE_LEFT | OUT_TOP_SIDE_RIGHT | OUT_LOW_FREQUENCY),
+    OUT_3POINT0POINT2 = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_FRONT_CENTER | OUT_TOP_SIDE_LEFT | OUT_TOP_SIDE_RIGHT),
+    OUT_3POINT1POINT2 = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_FRONT_CENTER | OUT_TOP_SIDE_LEFT | OUT_TOP_SIDE_RIGHT | OUT_LOW_FREQUENCY),
+    OUT_QUAD = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_BACK_LEFT | OUT_BACK_RIGHT),
+    OUT_QUAD_BACK = OUT_QUAD,
+    /**
+     * like OUT_QUAD_BACK with *_SIDE_* instead of *_BACK_*
+     */
+    OUT_QUAD_SIDE = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_SIDE_LEFT | OUT_SIDE_RIGHT),
+    OUT_SURROUND = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_FRONT_CENTER | OUT_BACK_CENTER),
+    OUT_PENTA = (OUT_QUAD | OUT_FRONT_CENTER),
+    OUT_5POINT1 = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_FRONT_CENTER | OUT_LOW_FREQUENCY | OUT_BACK_LEFT | OUT_BACK_RIGHT),
+    OUT_5POINT1_BACK = OUT_5POINT1,
+    /**
+     * like OUT_5POINT1_BACK with *_SIDE_* instead of *_BACK_*
+     */
+    OUT_5POINT1_SIDE = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_FRONT_CENTER | OUT_LOW_FREQUENCY | OUT_SIDE_LEFT | OUT_SIDE_RIGHT),
+    OUT_5POINT1POINT2 = (OUT_5POINT1 | OUT_TOP_SIDE_LEFT | OUT_TOP_SIDE_RIGHT),
+    OUT_5POINT1POINT4 = (OUT_5POINT1 | OUT_TOP_FRONT_LEFT | OUT_TOP_FRONT_RIGHT | OUT_TOP_BACK_LEFT | OUT_TOP_BACK_RIGHT),
+    OUT_6POINT1 = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_FRONT_CENTER | OUT_LOW_FREQUENCY | OUT_BACK_LEFT | OUT_BACK_RIGHT | OUT_BACK_CENTER),
+    /**
+     * matches the correct AudioFormat.CHANNEL_OUT_7POINT1_SURROUND
+     */
+    OUT_7POINT1 = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_FRONT_CENTER | OUT_LOW_FREQUENCY | OUT_BACK_LEFT | OUT_BACK_RIGHT | OUT_SIDE_LEFT | OUT_SIDE_RIGHT),
+    OUT_7POINT1POINT2 = (OUT_7POINT1 | OUT_TOP_SIDE_LEFT | OUT_TOP_SIDE_RIGHT),
+    OUT_7POINT1POINT4 = (OUT_7POINT1 | OUT_TOP_FRONT_LEFT | OUT_TOP_FRONT_RIGHT | OUT_TOP_BACK_LEFT | OUT_TOP_BACK_RIGHT),
+    OUT_MONO_HAPTIC_A = (OUT_FRONT_LEFT | OUT_HAPTIC_A),
+    OUT_STEREO_HAPTIC_A = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_HAPTIC_A),
+    OUT_HAPTIC_AB = (OUT_HAPTIC_A | OUT_HAPTIC_B),
+    OUT_MONO_HAPTIC_AB = (OUT_FRONT_LEFT | OUT_HAPTIC_A | OUT_HAPTIC_B),
+    OUT_STEREO_HAPTIC_AB = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_HAPTIC_A | OUT_HAPTIC_B),
+
     /**
      * These are bits only, not complete values
      *
diff --git a/media/aidl/android/media/audio/common/AudioConfig.aidl b/media/aidl/android/media/audio/common/AudioConfig.aidl
index 50dd796..d128561 100644
--- a/media/aidl/android/media/audio/common/AudioConfig.aidl
+++ b/media/aidl/android/media/audio/common/AudioConfig.aidl
@@ -27,10 +27,12 @@
  *
  * {@hide}
  */
+@JavaDerive(equals = true, toString = true)
+@VintfStability
 parcelable AudioConfig {
     int sampleRateHz;
     int channelMask;
-    AudioFormat format;
+    AudioFormat format = AudioFormat.INVALID;
     AudioOffloadInfo offloadInfo;
     long frameCount;
 }
diff --git a/media/aidl/android/media/audio/common/AudioFormat.aidl b/media/aidl/android/media/audio/common/AudioFormat.aidl
index aadc8e2..73fbca2 100644
--- a/media/aidl/android/media/audio/common/AudioFormat.aidl
+++ b/media/aidl/android/media/audio/common/AudioFormat.aidl
@@ -32,6 +32,7 @@
  *
  * {@hide}
  */
+@VintfStability
 @Backing(type="int")
 enum AudioFormat {
    INVALID = 0xFFFFFFFF,
diff --git a/media/aidl/android/media/audio/common/AudioOffloadInfo.aidl b/media/aidl/android/media/audio/common/AudioOffloadInfo.aidl
index ec10d71..7be5e6a 100644
--- a/media/aidl/android/media/audio/common/AudioOffloadInfo.aidl
+++ b/media/aidl/android/media/audio/common/AudioOffloadInfo.aidl
@@ -28,17 +28,19 @@
  *
  * {@hide}
  */
+@JavaDerive(equals = true, toString = true)
+@VintfStability
 parcelable AudioOffloadInfo {
     int sampleRateHz;
     int channelMask;
-    AudioFormat format;
-    AudioStreamType streamType;
+    AudioFormat format = AudioFormat.INVALID;
+    AudioStreamType streamType = AudioStreamType.INVALID;
     int bitRatePerSecond;
     long durationMicroseconds;
     boolean hasVideo;
     boolean isStreaming;
     int bitWidth;
     int bufferSize;
-    AudioUsage usage;
+    AudioUsage usage = AudioUsage.INVALID;
 }
 
diff --git a/media/aidl/android/media/audio/common/AudioStreamType.aidl b/media/aidl/android/media/audio/common/AudioStreamType.aidl
index c545667..8b70367 100644
--- a/media/aidl/android/media/audio/common/AudioStreamType.aidl
+++ b/media/aidl/android/media/audio/common/AudioStreamType.aidl
@@ -26,8 +26,14 @@
  *
  * {@hide}
  */
+@VintfStability
 @Backing(type="int")
 enum AudioStreamType {
+    /**
+     * Used as default value in parcelables to indicate that a value was not set.
+     * Should never be considered a valid setting, except for backward compatibility scenarios.
+     */
+    INVALID = -2,
     DEFAULT = -1,
     MIN = 0,
     VOICE_CALL = 0,
diff --git a/media/aidl/android/media/audio/common/AudioUsage.aidl b/media/aidl/android/media/audio/common/AudioUsage.aidl
index ef34816..028eefe 100644
--- a/media/aidl/android/media/audio/common/AudioUsage.aidl
+++ b/media/aidl/android/media/audio/common/AudioUsage.aidl
@@ -22,8 +22,14 @@
 /**
  * {@hide}
  */
+@VintfStability
 @Backing(type="int")
 enum AudioUsage {
+    /**
+     * Used as default value in parcelables to indicate that a value was not set.
+     * Should never be considered a valid setting, except for backward compatibility scenarios.
+     */
+    INVALID = -1,
     UNKNOWN = 0,
     MEDIA = 1,
     VOICE_COMMUNICATION = 2,
diff --git a/media/aidl/android/media/permission/Identity.aidl b/media/aidl/android/media/permission/Identity.aidl
index 3638904..5249786 100644
--- a/media/aidl/android/media/permission/Identity.aidl
+++ b/media/aidl/android/media/permission/Identity.aidl
@@ -20,6 +20,7 @@
  *
  * {@hide}
  */
+@JavaDerive(equals = true, toString = true)
 parcelable Identity {
     /** Linux user ID. */
     int uid = -1;
diff --git a/media/aidl/android/media/soundtrigger_middleware/AudioCapabilities.aidl b/media/aidl/android/media/soundtrigger/AudioCapabilities.aidl
similarity index 94%
rename from media/aidl/android/media/soundtrigger_middleware/AudioCapabilities.aidl
rename to media/aidl/android/media/soundtrigger/AudioCapabilities.aidl
index 97a8849..7b0825b 100644
--- a/media/aidl/android/media/soundtrigger_middleware/AudioCapabilities.aidl
+++ b/media/aidl/android/media/soundtrigger/AudioCapabilities.aidl
@@ -13,12 +13,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.media.soundtrigger_middleware;
+package android.media.soundtrigger;
 
 /**
  * AudioCapabilities supported by the implemented HAL driver.
  * @hide
  */
+@VintfStability
 @Backing(type="int")
 enum AudioCapabilities {
     /**
diff --git a/media/aidl/android/media/soundtrigger_middleware/ConfidenceLevel.aidl b/media/aidl/android/media/soundtrigger/ConfidenceLevel.aidl
similarity index 91%
rename from media/aidl/android/media/soundtrigger_middleware/ConfidenceLevel.aidl
rename to media/aidl/android/media/soundtrigger/ConfidenceLevel.aidl
index 3dbc705..3fcba40 100644
--- a/media/aidl/android/media/soundtrigger_middleware/ConfidenceLevel.aidl
+++ b/media/aidl/android/media/soundtrigger/ConfidenceLevel.aidl
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.media.soundtrigger_middleware;
+package android.media.soundtrigger;
 
 /**
  * A recognition confidence level.
@@ -21,6 +21,8 @@
  *
  * {@hide}
  */
+@JavaDerive(equals = true, toString = true)
+@VintfStability
 parcelable ConfidenceLevel {
     /** user ID. */
     int userId;
diff --git a/media/aidl/android/media/soundtrigger_middleware/ModelParameter.aidl b/media/aidl/android/media/soundtrigger/ModelParameter.aidl
similarity index 95%
rename from media/aidl/android/media/soundtrigger_middleware/ModelParameter.aidl
rename to media/aidl/android/media/soundtrigger/ModelParameter.aidl
index 0993627..9484008 100644
--- a/media/aidl/android/media/soundtrigger_middleware/ModelParameter.aidl
+++ b/media/aidl/android/media/soundtrigger/ModelParameter.aidl
@@ -13,13 +13,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.media.soundtrigger_middleware;
+package android.media.soundtrigger;
 
 /**
  * Model specific parameters to be used with parameter set and get APIs.
  *
  * {@hide}
  */
+@VintfStability
 @Backing(type="int")
 enum ModelParameter {
     /**
diff --git a/media/aidl/android/media/soundtrigger_middleware/ModelParameterRange.aidl b/media/aidl/android/media/soundtrigger/ModelParameterRange.aidl
similarity index 89%
rename from media/aidl/android/media/soundtrigger_middleware/ModelParameterRange.aidl
rename to media/aidl/android/media/soundtrigger/ModelParameterRange.aidl
index d6948a8..e7c616b 100644
--- a/media/aidl/android/media/soundtrigger_middleware/ModelParameterRange.aidl
+++ b/media/aidl/android/media/soundtrigger/ModelParameterRange.aidl
@@ -13,13 +13,15 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.media.soundtrigger_middleware;
+package android.media.soundtrigger;
 
 /**
  * Value range for a model parameter.
  *
  * {@hide}
  */
+@JavaDerive(equals = true, toString = true)
+@VintfStability
 parcelable ModelParameterRange {
     /** Minimum (inclusive) */
     int minInclusive;
diff --git a/media/aidl/android/media/soundtrigger_middleware/Phrase.aidl b/media/aidl/android/media/soundtrigger/Phrase.aidl
similarity index 91%
rename from media/aidl/android/media/soundtrigger_middleware/Phrase.aidl
rename to media/aidl/android/media/soundtrigger/Phrase.aidl
index 98a489f8..077db21 100644
--- a/media/aidl/android/media/soundtrigger_middleware/Phrase.aidl
+++ b/media/aidl/android/media/soundtrigger/Phrase.aidl
@@ -13,13 +13,15 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.media.soundtrigger_middleware;
+package android.media.soundtrigger;
 
 /**
  * Key phrase descriptor.
  *
  * {@hide}
  */
+@JavaDerive(equals = true, toString = true)
+@VintfStability
 parcelable Phrase {
     /** Unique keyphrase ID assigned at enrollment time. */
     int id;
diff --git a/media/aidl/android/media/soundtrigger_middleware/PhraseRecognitionEvent.aidl b/media/aidl/android/media/soundtrigger/PhraseRecognitionEvent.aidl
similarity index 82%
rename from media/aidl/android/media/soundtrigger_middleware/PhraseRecognitionEvent.aidl
rename to media/aidl/android/media/soundtrigger/PhraseRecognitionEvent.aidl
index 6a3ec61..654f7c2 100644
--- a/media/aidl/android/media/soundtrigger_middleware/PhraseRecognitionEvent.aidl
+++ b/media/aidl/android/media/soundtrigger/PhraseRecognitionEvent.aidl
@@ -13,16 +13,18 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.media.soundtrigger_middleware;
+package android.media.soundtrigger;
 
-import android.media.soundtrigger_middleware.PhraseRecognitionExtra;
-import android.media.soundtrigger_middleware.RecognitionEvent;
+import android.media.soundtrigger.PhraseRecognitionExtra;
+import android.media.soundtrigger.RecognitionEvent;
 
 /**
  * An event that gets sent to indicate a phrase recognition (or aborting of the recognition
    process).
  * {@hide}
  */
+@JavaDerive(equals = true, toString = true)
+@VintfStability
 parcelable PhraseRecognitionEvent {
     /** Common recognition event. */
     RecognitionEvent common;
diff --git a/media/aidl/android/media/soundtrigger_middleware/PhraseRecognitionExtra.aidl b/media/aidl/android/media/soundtrigger/PhraseRecognitionExtra.aidl
similarity index 69%
rename from media/aidl/android/media/soundtrigger_middleware/PhraseRecognitionExtra.aidl
rename to media/aidl/android/media/soundtrigger/PhraseRecognitionExtra.aidl
index cb96bf3..eb523eb 100644
--- a/media/aidl/android/media/soundtrigger_middleware/PhraseRecognitionExtra.aidl
+++ b/media/aidl/android/media/soundtrigger/PhraseRecognitionExtra.aidl
@@ -13,23 +13,23 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.media.soundtrigger_middleware;
+package android.media.soundtrigger;
 
-import android.media.soundtrigger_middleware.ConfidenceLevel;
+import android.media.soundtrigger.ConfidenceLevel;
 
 /**
  * Specialized recognition event for key phrase detection.
  * {@hide}
  */
+@JavaDerive(equals = true, toString = true)
+@VintfStability
 parcelable PhraseRecognitionExtra {
-    // TODO(ytai): Constants / enums.
-
-    /** keyphrase ID */
+    /** Keyphrase ID */
     int id;
-    /** recognition modes used for this keyphrase */
+    /** Bitfield, indexed by RecognitionMode. */
     int recognitionModes;
-    /** confidence level for mode RECOGNITION_MODE_VOICE_TRIGGER */
+    /** Confidence level for mode RECOGNITION_MODE_VOICE_TRIGGER. Value is between 0-100. */
     int confidenceLevel;
-    /** number of user confidence levels */
+    /** Number of user confidence levels */
     ConfidenceLevel[] levels;
 }
diff --git a/media/aidl/android/media/soundtrigger_middleware/PhraseSoundModel.aidl b/media/aidl/android/media/soundtrigger/PhraseSoundModel.aidl
similarity index 84%
rename from media/aidl/android/media/soundtrigger_middleware/PhraseSoundModel.aidl
rename to media/aidl/android/media/soundtrigger/PhraseSoundModel.aidl
index 81028c1..e0ffdee 100644
--- a/media/aidl/android/media/soundtrigger_middleware/PhraseSoundModel.aidl
+++ b/media/aidl/android/media/soundtrigger/PhraseSoundModel.aidl
@@ -13,10 +13,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.media.soundtrigger_middleware;
+package android.media.soundtrigger;
 
-import android.media.soundtrigger_middleware.SoundModel;
-import android.media.soundtrigger_middleware.Phrase;
+import android.media.soundtrigger.SoundModel;
+import android.media.soundtrigger.Phrase;
 
 /**
  * Specialized sound model for key phrase detection.
@@ -24,6 +24,8 @@
  * information indicated by phrases field.
  * {@hide}
  */
+@JavaDerive(equals = true, toString = true)
+@VintfStability
 parcelable PhraseSoundModel {
     /** Common part of sound model descriptor */
     SoundModel common;
diff --git a/media/aidl/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl b/media/aidl/android/media/soundtrigger/Properties.aidl
similarity index 92%
rename from media/aidl/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl
rename to media/aidl/android/media/soundtrigger/Properties.aidl
index 9c56e7b..efa1b6a 100644
--- a/media/aidl/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl
+++ b/media/aidl/android/media/soundtrigger/Properties.aidl
@@ -13,13 +13,15 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.media.soundtrigger_middleware;
+package android.media.soundtrigger;
 
 /**
  * Capabilities of a sound trigger module.
  * {@hide}
  */
-parcelable SoundTriggerModuleProperties {
+@JavaDerive(equals = true, toString = true)
+@VintfStability
+parcelable Properties {
     /** Implementor name */
     String   implementor;
     /** Implementation description */
@@ -44,7 +46,7 @@
     int maxKeyPhrases;
     /** Maximum number of concurrent users detected */
     int maxUsers;
-    /** All supported modes. e.g RecognitionMode.VOICE_TRIGGER */
+    /** All supported modes. Bitfield, indexed by RecognitionMode. */
     int recognitionModes;
     /** Supports seamless transition from detection to capture */
     boolean     captureTransition;
diff --git a/media/aidl/android/media/soundtrigger_middleware/RecognitionConfig.aidl b/media/aidl/android/media/soundtrigger/RecognitionConfig.aidl
similarity index 82%
rename from media/aidl/android/media/soundtrigger_middleware/RecognitionConfig.aidl
rename to media/aidl/android/media/soundtrigger/RecognitionConfig.aidl
index 5c0eeb1..a00f0e5 100644
--- a/media/aidl/android/media/soundtrigger_middleware/RecognitionConfig.aidl
+++ b/media/aidl/android/media/soundtrigger/RecognitionConfig.aidl
@@ -13,14 +13,16 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.media.soundtrigger_middleware;
+package android.media.soundtrigger;
 
-import android.media.soundtrigger_middleware.PhraseRecognitionExtra;
+import android.media.soundtrigger.PhraseRecognitionExtra;
 
 /**
  * Configuration for tuning behavior of an active recognition process.
  * {@hide}
  */
+@JavaDerive(equals = true, toString = true)
+@VintfStability
 parcelable RecognitionConfig {
     /* Capture and buffer audio for this recognition instance. */
     boolean captureRequested;
@@ -34,6 +36,6 @@
      */
     int audioCapabilities;
 
-    /** Opaque capture configuration data. */
+    /** Capture configuration data. Content is implementation-defined. */
     byte[] data;
 }
diff --git a/media/aidl/android/media/soundtrigger_middleware/RecognitionEvent.aidl b/media/aidl/android/media/soundtrigger/RecognitionEvent.aidl
similarity index 84%
rename from media/aidl/android/media/soundtrigger_middleware/RecognitionEvent.aidl
rename to media/aidl/android/media/soundtrigger/RecognitionEvent.aidl
index a237ec1..94668a3 100644
--- a/media/aidl/android/media/soundtrigger_middleware/RecognitionEvent.aidl
+++ b/media/aidl/android/media/soundtrigger/RecognitionEvent.aidl
@@ -13,25 +13,25 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.media.soundtrigger_middleware;
+package android.media.soundtrigger;
 
 import android.media.audio.common.AudioConfig;
-import android.media.soundtrigger_middleware.RecognitionStatus;
-import android.media.soundtrigger_middleware.SoundModelType;
+import android.media.soundtrigger.RecognitionStatus;
+import android.media.soundtrigger.SoundModelType;
 
 /**
  * An event that gets sent to indicate a recognition (or aborting of the recognition process).
  * {@hide}
  */
+@JavaDerive(equals = true, toString = true)
+@VintfStability
 parcelable RecognitionEvent {
     /** Recognition status. */
-    RecognitionStatus status;
+    RecognitionStatus status = RecognitionStatus.INVALID;
     /** Event type, same as sound model type. */
-    SoundModelType type;
+    SoundModelType type = SoundModelType.INVALID;
     /** Is it possible to capture audio from this utterance buffered by the implementation. */
     boolean captureAvailable;
-    /* Audio session ID. framework use. */
-    int captureSession;
     /**
      * Delay in ms between end of model detection and start of audio available for capture.
      * A negative value is possible (e.g. if key phrase is also available for Capture.
diff --git a/media/aidl/android/media/soundtrigger_middleware/RecognitionMode.aidl b/media/aidl/android/media/soundtrigger/RecognitionMode.aidl
similarity index 94%
rename from media/aidl/android/media/soundtrigger_middleware/RecognitionMode.aidl
rename to media/aidl/android/media/soundtrigger/RecognitionMode.aidl
index d8bfff4..ce2cffe 100644
--- a/media/aidl/android/media/soundtrigger_middleware/RecognitionMode.aidl
+++ b/media/aidl/android/media/soundtrigger/RecognitionMode.aidl
@@ -13,12 +13,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.media.soundtrigger_middleware;
+package android.media.soundtrigger;
 
 /**
  * Recognition mode.
  * {@hide}
  */
+@VintfStability
 @Backing(type="int")
 enum RecognitionMode {
     /** Simple voice trigger. */
diff --git a/media/aidl/android/media/soundtrigger_middleware/RecognitionStatus.aidl b/media/aidl/android/media/soundtrigger/RecognitionStatus.aidl
similarity index 81%
rename from media/aidl/android/media/soundtrigger_middleware/RecognitionStatus.aidl
rename to media/aidl/android/media/soundtrigger/RecognitionStatus.aidl
index d563edc..cccf0f3 100644
--- a/media/aidl/android/media/soundtrigger_middleware/RecognitionStatus.aidl
+++ b/media/aidl/android/media/soundtrigger/RecognitionStatus.aidl
@@ -13,14 +13,20 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.media.soundtrigger_middleware;
+package android.media.soundtrigger;
 
 /**
  * A status for indicating the type of a recognition event.
  * {@hide}
  */
+@VintfStability
 @Backing(type="int")
 enum RecognitionStatus {
+    /**
+     * Used as default value in parcelables to indicate that a value was not set.
+     * Should never be considered a valid setting, except for backward compatibility scenarios.
+     */
+    INVALID = -1,
     /** Recognition success. */
     SUCCESS = 0,
     /** Recognition aborted (e.g. capture preempted by another use-case. */
diff --git a/media/aidl/android/media/soundtrigger_middleware/SoundModel.aidl b/media/aidl/android/media/soundtrigger/SoundModel.aidl
similarity index 85%
rename from media/aidl/android/media/soundtrigger_middleware/SoundModel.aidl
rename to media/aidl/android/media/soundtrigger/SoundModel.aidl
index 8186fb7..94244d0 100644
--- a/media/aidl/android/media/soundtrigger_middleware/SoundModel.aidl
+++ b/media/aidl/android/media/soundtrigger/SoundModel.aidl
@@ -13,9 +13,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.media.soundtrigger_middleware;
+package android.media.soundtrigger;
 
-import android.media.soundtrigger_middleware.SoundModelType;
+import android.media.soundtrigger.SoundModelType;
 import android.os.ParcelFileDescriptor;
 
 /**
@@ -23,9 +23,11 @@
  * aggregation.
  * {@hide}
  */
+@JavaDerive(equals = true, toString = true)
+@VintfStability
 parcelable SoundModel {
     /** Model type. */
-    SoundModelType type;
+    SoundModelType type = SoundModelType.INVALID;
     /** Unique sound model ID. */
     String uuid;
     /**
diff --git a/media/aidl/android/media/soundtrigger_middleware/SoundModelType.aidl b/media/aidl/android/media/soundtrigger/SoundModelType.aidl
similarity index 75%
rename from media/aidl/android/media/soundtrigger_middleware/SoundModelType.aidl
rename to media/aidl/android/media/soundtrigger/SoundModelType.aidl
index f2abc9a..34a9376 100644
--- a/media/aidl/android/media/soundtrigger_middleware/SoundModelType.aidl
+++ b/media/aidl/android/media/soundtrigger/SoundModelType.aidl
@@ -13,16 +13,20 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.media.soundtrigger_middleware;
+package android.media.soundtrigger;
 
 /**
  * Sound model type.
  * {@hide}
  */
+@VintfStability
 @Backing(type="int")
 enum SoundModelType {
-    /** Unspecified sound model type */
-    UNKNOWN = -1,
+    /**
+     * Used as default value in parcelables to indicate that a value was not set.
+     * Should never be considered a valid setting, except for backward compatibility scenarios.
+     */
+    INVALID = -1,
     /** Key phrase sound models */
     KEYPHRASE = 0,
     /** All models other than keyphrase */
diff --git a/media/aidl/android/media/soundtrigger_middleware/Status.aidl b/media/aidl/android/media/soundtrigger/Status.aidl
similarity index 83%
rename from media/aidl/android/media/soundtrigger_middleware/Status.aidl
rename to media/aidl/android/media/soundtrigger/Status.aidl
index c7623f5..ca1487f 100644
--- a/media/aidl/android/media/soundtrigger_middleware/Status.aidl
+++ b/media/aidl/android/media/soundtrigger/Status.aidl
@@ -13,13 +13,19 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.media.soundtrigger_middleware;
+package android.media.soundtrigger;
 
 /**
  * {@hide}
  */
+@VintfStability
 @Backing(type="int")
 enum Status {
+    /**
+     * Used as default value in parcelables to indicate that a value was not set.
+     * Should never be considered a valid setting, except for backward compatibility scenarios.
+     */
+    INVALID = -1,
     /** Success. */
     SUCCESS = 0,
     /** Failure due to resource contention. This is typically a temporary condition. */
diff --git a/media/aidl/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl b/media/aidl/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl
index 726af76..6092ac5 100644
--- a/media/aidl/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl
+++ b/media/aidl/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl
@@ -15,8 +15,8 @@
  */
 package android.media.soundtrigger_middleware;
 
-import android.media.soundtrigger_middleware.RecognitionEvent;
-import android.media.soundtrigger_middleware.PhraseRecognitionEvent;
+import android.media.soundtrigger.RecognitionEvent;
+import android.media.soundtrigger.PhraseRecognitionEvent;
 
 /**
  * Main interface for a client to get notifications of events coming from this module.
@@ -28,22 +28,29 @@
      * Invoked whenever a recognition event is triggered (typically, on recognition, but also in
      * case of external aborting of a recognition or a forced recognition event - see the status
      * code in the event for determining).
+     * In case of abortion, the caller may retry after the next onRecognitionAvailabilityChange()
+     * callback.
      */
-    void onRecognition(int modelHandle, in RecognitionEvent event);
+    void onRecognition(int modelHandle, in RecognitionEvent event, int captureSession);
      /**
       * Invoked whenever a phrase recognition event is triggered (typically, on recognition, but
       * also in case of external aborting of a recognition or a forced recognition event - see the
       * status code in the event for determining).
+      * In case of abortion, the caller may retry after the next onRecognitionAvailabilityChange()
+      * callback.
       */
-    void onPhraseRecognition(int modelHandle, in PhraseRecognitionEvent event);
+    void onPhraseRecognition(int modelHandle, in PhraseRecognitionEvent event, int captureSession);
     /**
-     * Notifies the client the recognition has become available after previously having been
-     * unavailable, or vice versa. This method will always be invoked once immediately after
-     * attachment, and then every time there is a change in availability.
-     * When availability changes from available to unavailable, all active recognitions are aborted,
-     * and this event will be sent in addition to the abort event.
+     * Notifies the client that some start/load operations that have previously failed for resource
+     * reasons (threw a ServiceSpecificException(RESOURCE_CONTENTION) or have been preempted) may
+     * now succeed. This is not a guarantee, but a hint for the client to retry.
      */
-    void onRecognitionAvailabilityChange(boolean available);
+    void onResourcesAvailable();
+    /**
+     * Notifies the client that a model had been preemptively unloaded by the service.
+     * The caller may retry after the next onRecognitionAvailabilityChange() callback.
+     */
+    void onModelUnloaded(int modelHandle);
     /**
      * Notifies the client that the associated module has crashed and restarted. The module instance
      * is no longer usable and will throw a ServiceSpecificException with a Status.DEAD_OBJECT code
diff --git a/media/aidl/android/media/soundtrigger_middleware/ISoundTriggerModule.aidl b/media/aidl/android/media/soundtrigger_middleware/ISoundTriggerModule.aidl
index c4a5785..0b46fd4 100644
--- a/media/aidl/android/media/soundtrigger_middleware/ISoundTriggerModule.aidl
+++ b/media/aidl/android/media/soundtrigger_middleware/ISoundTriggerModule.aidl
@@ -15,11 +15,11 @@
  */
 package android.media.soundtrigger_middleware;
 
-import android.media.soundtrigger_middleware.ModelParameter;
-import android.media.soundtrigger_middleware.ModelParameterRange;
-import android.media.soundtrigger_middleware.SoundModel;
-import android.media.soundtrigger_middleware.PhraseSoundModel;
-import android.media.soundtrigger_middleware.RecognitionConfig;
+import android.media.soundtrigger.ModelParameter;
+import android.media.soundtrigger.ModelParameterRange;
+import android.media.soundtrigger.SoundModel;
+import android.media.soundtrigger.PhraseSoundModel;
+import android.media.soundtrigger.RecognitionConfig;
 
 /**
  * A sound-trigger module.
@@ -75,6 +75,9 @@
      * Once a recognition event is passed to the client, the recognition automatically become
      * inactive, unless the event is of the RecognitionStatus.FORCED kind. Client can also shut down
      * the recognition explicitly, via stopRecognition.
+     *
+     * May throw a ServiceSpecificException with an RESOURCE_CONTENTION status to indicate that
+     * resources required for starting the model are currently consumed by other clients.
      */
     void startRecognition(int modelHandle, in RecognitionConfig config);
 
diff --git a/media/aidl/android/media/soundtrigger_middleware/SoundTriggerModuleDescriptor.aidl b/media/aidl/android/media/soundtrigger_middleware/SoundTriggerModuleDescriptor.aidl
index 667135f..6c210bf 100644
--- a/media/aidl/android/media/soundtrigger_middleware/SoundTriggerModuleDescriptor.aidl
+++ b/media/aidl/android/media/soundtrigger_middleware/SoundTriggerModuleDescriptor.aidl
@@ -15,17 +15,18 @@
  */
 package android.media.soundtrigger_middleware;
 
-import android.media.soundtrigger_middleware.SoundTriggerModuleProperties;
+import android.media.soundtrigger.Properties;
 
 /**
  * A descriptor of an available sound trigger module, containing the handle used to reference the
  * module, as well its capabilities.
  * {@hide}
  */
+@JavaDerive(equals = true, toString = true)
 parcelable SoundTriggerModuleDescriptor {
     /** Module handle to be used for attaching to it. */
     int handle;
     /** Module capabilities. */
-    SoundTriggerModuleProperties properties;
+    Properties properties;
 }
 
diff --git a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioChannelMask.aidl b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioChannelMask.aidl
new file mode 100644
index 0000000..c3af3bf
--- /dev/null
+++ b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioChannelMask.aidl
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */// This file has been semi-automatically generated using hidl2aidl from its counterpart in
+// hardware/interfaces/audio/common/5.0/types.hal
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.audio.common;
+/* @hide */
+@Backing(type="int") @VintfStability
+enum AudioChannelMask {
+  REPRESENTATION_POSITION = 0,
+  REPRESENTATION_INDEX = 2,
+  NONE = 0,
+  INVALID = -1073741824,
+  OUT_FRONT_LEFT = 1,
+  OUT_FRONT_RIGHT = 2,
+  OUT_FRONT_CENTER = 4,
+  OUT_LOW_FREQUENCY = 8,
+  OUT_BACK_LEFT = 16,
+  OUT_BACK_RIGHT = 32,
+  OUT_FRONT_LEFT_OF_CENTER = 64,
+  OUT_FRONT_RIGHT_OF_CENTER = 128,
+  OUT_BACK_CENTER = 256,
+  OUT_SIDE_LEFT = 512,
+  OUT_SIDE_RIGHT = 1024,
+  OUT_TOP_CENTER = 2048,
+  OUT_TOP_FRONT_LEFT = 4096,
+  OUT_TOP_FRONT_CENTER = 8192,
+  OUT_TOP_FRONT_RIGHT = 16384,
+  OUT_TOP_BACK_LEFT = 32768,
+  OUT_TOP_BACK_CENTER = 65536,
+  OUT_TOP_BACK_RIGHT = 131072,
+  OUT_TOP_SIDE_LEFT = 262144,
+  OUT_TOP_SIDE_RIGHT = 524288,
+  OUT_HAPTIC_A = 536870912,
+  OUT_HAPTIC_B = 268435456,
+  OUT_MONO = 1,
+  OUT_STEREO = 3,
+  OUT_2POINT1 = 11,
+  OUT_2POINT0POINT2 = 786435,
+  OUT_2POINT1POINT2 = 786443,
+  OUT_3POINT0POINT2 = 786439,
+  OUT_3POINT1POINT2 = 786447,
+  OUT_QUAD = 51,
+  OUT_QUAD_BACK = 51,
+  OUT_QUAD_SIDE = 1539,
+  OUT_SURROUND = 263,
+  OUT_PENTA = 55,
+  OUT_5POINT1 = 63,
+  OUT_5POINT1_BACK = 63,
+  OUT_5POINT1_SIDE = 1551,
+  OUT_5POINT1POINT2 = 786495,
+  OUT_5POINT1POINT4 = 184383,
+  OUT_6POINT1 = 319,
+  OUT_7POINT1 = 1599,
+  OUT_7POINT1POINT2 = 788031,
+  OUT_7POINT1POINT4 = 185919,
+  OUT_MONO_HAPTIC_A = 536870913,
+  OUT_STEREO_HAPTIC_A = 536870915,
+  OUT_HAPTIC_AB = 805306368,
+  OUT_MONO_HAPTIC_AB = 805306369,
+  OUT_STEREO_HAPTIC_AB = 805306371,
+  IN_LEFT = 4,
+  IN_RIGHT = 8,
+  IN_FRONT = 16,
+  IN_BACK = 32,
+  IN_LEFT_PROCESSED = 64,
+  IN_RIGHT_PROCESSED = 128,
+  IN_FRONT_PROCESSED = 256,
+  IN_BACK_PROCESSED = 512,
+  IN_PRESSURE = 1024,
+  IN_X_AXIS = 2048,
+  IN_Y_AXIS = 4096,
+  IN_Z_AXIS = 8192,
+  IN_BACK_LEFT = 65536,
+  IN_BACK_RIGHT = 131072,
+  IN_CENTER = 262144,
+  IN_LOW_FREQUENCY = 1048576,
+  IN_TOP_LEFT = 2097152,
+  IN_TOP_RIGHT = 4194304,
+  IN_VOICE_UPLINK = 16384,
+  IN_VOICE_DNLINK = 32768,
+}
diff --git a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioConfig.aidl b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioConfig.aidl
new file mode 100644
index 0000000..c11eb76
--- /dev/null
+++ b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioConfig.aidl
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */// This file has been semi-automatically generated using hidl2aidl from its counterpart in
+// hardware/interfaces/audio/common/5.0/types.hal
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.audio.common;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable AudioConfig {
+  int sampleRateHz;
+  int channelMask;
+  android.media.audio.common.AudioFormat format = android.media.audio.common.AudioFormat.INVALID;
+  android.media.audio.common.AudioOffloadInfo offloadInfo;
+  long frameCount;
+}
diff --git a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioFormat.aidl b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioFormat.aidl
new file mode 100644
index 0000000..b7c8659
--- /dev/null
+++ b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioFormat.aidl
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */// This file has been semi-automatically generated using hidl2aidl from its counterpart in
+// hardware/interfaces/audio/common/5.0/types.hal
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.audio.common;
+/* @hide */
+@Backing(type="int") @VintfStability
+enum AudioFormat {
+  INVALID = -1,
+  DEFAULT = 0,
+  PCM = 0,
+  MP3 = 16777216,
+  AMR_NB = 33554432,
+  AMR_WB = 50331648,
+  AAC = 67108864,
+  HE_AAC_V1 = 83886080,
+  HE_AAC_V2 = 100663296,
+  VORBIS = 117440512,
+  OPUS = 134217728,
+  AC3 = 150994944,
+  E_AC3 = 167772160,
+  DTS = 184549376,
+  DTS_HD = 201326592,
+  IEC61937 = 218103808,
+  DOLBY_TRUEHD = 234881024,
+  EVRC = 268435456,
+  EVRCB = 285212672,
+  EVRCWB = 301989888,
+  EVRCNW = 318767104,
+  AAC_ADIF = 335544320,
+  WMA = 352321536,
+  WMA_PRO = 369098752,
+  AMR_WB_PLUS = 385875968,
+  MP2 = 402653184,
+  QCELP = 419430400,
+  DSD = 436207616,
+  FLAC = 452984832,
+  ALAC = 469762048,
+  APE = 486539264,
+  AAC_ADTS = 503316480,
+  SBC = 520093696,
+  APTX = 536870912,
+  APTX_HD = 553648128,
+  AC4 = 570425344,
+  LDAC = 587202560,
+  MAT = 603979776,
+  AAC_LATM = 620756992,
+  CELT = 637534208,
+  APTX_ADAPTIVE = 654311424,
+  LHDC = 671088640,
+  LHDC_LL = 687865856,
+  APTX_TWSP = 704643072,
+  MAIN_MASK = -16777216,
+  SUB_MASK = 16777215,
+  PCM_SUB_16_BIT = 1,
+  PCM_SUB_8_BIT = 2,
+  PCM_SUB_32_BIT = 3,
+  PCM_SUB_8_24_BIT = 4,
+  PCM_SUB_FLOAT = 5,
+  PCM_SUB_24_BIT_PACKED = 6,
+  MP3_SUB_NONE = 0,
+  AMR_SUB_NONE = 0,
+  AAC_SUB_MAIN = 1,
+  AAC_SUB_LC = 2,
+  AAC_SUB_SSR = 4,
+  AAC_SUB_LTP = 8,
+  AAC_SUB_HE_V1 = 16,
+  AAC_SUB_SCALABLE = 32,
+  AAC_SUB_ERLC = 64,
+  AAC_SUB_LD = 128,
+  AAC_SUB_HE_V2 = 256,
+  AAC_SUB_ELD = 512,
+  AAC_SUB_XHE = 768,
+  VORBIS_SUB_NONE = 0,
+  E_AC3_SUB_JOC = 1,
+  MAT_SUB_1_0 = 1,
+  MAT_SUB_2_0 = 2,
+  MAT_SUB_2_1 = 3,
+}
diff --git a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioOffloadInfo.aidl b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioOffloadInfo.aidl
new file mode 100644
index 0000000..b5d889e
--- /dev/null
+++ b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioOffloadInfo.aidl
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */// This file has been semi-automatically generated using hidl2aidl from its counterpart in
+// hardware/interfaces/audio/common/5.0/types.hal
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.audio.common;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable AudioOffloadInfo {
+  int sampleRateHz;
+  int channelMask;
+  android.media.audio.common.AudioFormat format = android.media.audio.common.AudioFormat.INVALID;
+  android.media.audio.common.AudioStreamType streamType = android.media.audio.common.AudioStreamType.INVALID;
+  int bitRatePerSecond;
+  long durationMicroseconds;
+  boolean hasVideo;
+  boolean isStreaming;
+  int bitWidth;
+  int bufferSize;
+  android.media.audio.common.AudioUsage usage = android.media.audio.common.AudioUsage.INVALID;
+}
diff --git a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioStreamType.aidl b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioStreamType.aidl
new file mode 100644
index 0000000..915c668
--- /dev/null
+++ b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioStreamType.aidl
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */// This file has been semi-automatically generated using hidl2aidl from its counterpart in
+// hardware/interfaces/audio/common/5.0/types.hal
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.audio.common;
+/* @hide */
+@Backing(type="int") @VintfStability
+enum AudioStreamType {
+  INVALID = -2,
+  DEFAULT = -1,
+  MIN = 0,
+  VOICE_CALL = 0,
+  SYSTEM = 1,
+  RING = 2,
+  MUSIC = 3,
+  ALARM = 4,
+  NOTIFICATION = 5,
+  BLUETOOTH_SCO = 6,
+  ENFORCED_AUDIBLE = 7,
+  DTMF = 8,
+  TTS = 9,
+  ACCESSIBILITY = 10,
+}
diff --git a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioUsage.aidl b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioUsage.aidl
new file mode 100644
index 0000000..f5130a4
--- /dev/null
+++ b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioUsage.aidl
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */// This file has been semi-automatically generated using hidl2aidl from its counterpart in
+// hardware/interfaces/audio/common/5.0/types.hal
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.audio.common;
+/* @hide */
+@Backing(type="int") @VintfStability
+enum AudioUsage {
+  INVALID = -1,
+  UNKNOWN = 0,
+  MEDIA = 1,
+  VOICE_COMMUNICATION = 2,
+  VOICE_COMMUNICATION_SIGNALLING = 3,
+  ALARM = 4,
+  NOTIFICATION = 5,
+  NOTIFICATION_TELEPHONY_RINGTONE = 6,
+  ASSISTANCE_ACCESSIBILITY = 11,
+  ASSISTANCE_NAVIGATION_GUIDANCE = 12,
+  ASSISTANCE_SONIFICATION = 13,
+  GAME = 14,
+  VIRTUAL_SOURCE = 15,
+  ASSISTANT = 16,
+}
diff --git a/media/aidl_api/android.media.soundtrigger.types/current/android/media/soundtrigger/AudioCapabilities.aidl b/media/aidl_api/android.media.soundtrigger.types/current/android/media/soundtrigger/AudioCapabilities.aidl
new file mode 100644
index 0000000..5d88305
--- /dev/null
+++ b/media/aidl_api/android.media.soundtrigger.types/current/android/media/soundtrigger/AudioCapabilities.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.soundtrigger;
+/* @hide */
+@Backing(type="int") @VintfStability
+enum AudioCapabilities {
+  ECHO_CANCELLATION = 1,
+  NOISE_SUPPRESSION = 2,
+}
diff --git a/media/aidl_api/android.media.soundtrigger.types/current/android/media/soundtrigger/ConfidenceLevel.aidl b/media/aidl_api/android.media.soundtrigger.types/current/android/media/soundtrigger/ConfidenceLevel.aidl
new file mode 100644
index 0000000..5127a11
--- /dev/null
+++ b/media/aidl_api/android.media.soundtrigger.types/current/android/media/soundtrigger/ConfidenceLevel.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.soundtrigger;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable ConfidenceLevel {
+  int userId;
+  int levelPercent;
+}
diff --git a/media/aidl_api/android.media.soundtrigger.types/current/android/media/soundtrigger/ModelParameter.aidl b/media/aidl_api/android.media.soundtrigger.types/current/android/media/soundtrigger/ModelParameter.aidl
new file mode 100644
index 0000000..aadbf80
--- /dev/null
+++ b/media/aidl_api/android.media.soundtrigger.types/current/android/media/soundtrigger/ModelParameter.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.soundtrigger;
+/* @hide */
+@Backing(type="int") @VintfStability
+enum ModelParameter {
+  INVALID = -1,
+  THRESHOLD_FACTOR = 0,
+}
diff --git a/media/aidl_api/android.media.soundtrigger.types/current/android/media/soundtrigger/ModelParameterRange.aidl b/media/aidl_api/android.media.soundtrigger.types/current/android/media/soundtrigger/ModelParameterRange.aidl
new file mode 100644
index 0000000..f29b728
--- /dev/null
+++ b/media/aidl_api/android.media.soundtrigger.types/current/android/media/soundtrigger/ModelParameterRange.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.soundtrigger;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable ModelParameterRange {
+  int minInclusive;
+  int maxInclusive;
+}
diff --git a/media/aidl_api/android.media.soundtrigger.types/current/android/media/soundtrigger/Phrase.aidl b/media/aidl_api/android.media.soundtrigger.types/current/android/media/soundtrigger/Phrase.aidl
new file mode 100644
index 0000000..11029ba
--- /dev/null
+++ b/media/aidl_api/android.media.soundtrigger.types/current/android/media/soundtrigger/Phrase.aidl
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.soundtrigger;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable Phrase {
+  int id;
+  int recognitionModes;
+  int[] users;
+  String locale;
+  String text;
+}
diff --git a/media/aidl_api/android.media.soundtrigger.types/current/android/media/soundtrigger/PhraseRecognitionEvent.aidl b/media/aidl_api/android.media.soundtrigger.types/current/android/media/soundtrigger/PhraseRecognitionEvent.aidl
new file mode 100644
index 0000000..b75d1b8
--- /dev/null
+++ b/media/aidl_api/android.media.soundtrigger.types/current/android/media/soundtrigger/PhraseRecognitionEvent.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.soundtrigger;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable PhraseRecognitionEvent {
+  android.media.soundtrigger.RecognitionEvent common;
+  android.media.soundtrigger.PhraseRecognitionExtra[] phraseExtras;
+}
diff --git a/media/aidl_api/android.media.soundtrigger.types/current/android/media/soundtrigger/PhraseRecognitionExtra.aidl b/media/aidl_api/android.media.soundtrigger.types/current/android/media/soundtrigger/PhraseRecognitionExtra.aidl
new file mode 100644
index 0000000..e417c69
--- /dev/null
+++ b/media/aidl_api/android.media.soundtrigger.types/current/android/media/soundtrigger/PhraseRecognitionExtra.aidl
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.soundtrigger;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable PhraseRecognitionExtra {
+  int id;
+  int recognitionModes;
+  int confidenceLevel;
+  android.media.soundtrigger.ConfidenceLevel[] levels;
+}
diff --git a/media/aidl_api/android.media.soundtrigger.types/current/android/media/soundtrigger/PhraseSoundModel.aidl b/media/aidl_api/android.media.soundtrigger.types/current/android/media/soundtrigger/PhraseSoundModel.aidl
new file mode 100644
index 0000000..b4b3854
--- /dev/null
+++ b/media/aidl_api/android.media.soundtrigger.types/current/android/media/soundtrigger/PhraseSoundModel.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.soundtrigger;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable PhraseSoundModel {
+  android.media.soundtrigger.SoundModel common;
+  android.media.soundtrigger.Phrase[] phrases;
+}
diff --git a/media/aidl_api/android.media.soundtrigger.types/current/android/media/soundtrigger/Properties.aidl b/media/aidl_api/android.media.soundtrigger.types/current/android/media/soundtrigger/Properties.aidl
new file mode 100644
index 0000000..068db52
--- /dev/null
+++ b/media/aidl_api/android.media.soundtrigger.types/current/android/media/soundtrigger/Properties.aidl
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.soundtrigger;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable Properties {
+  String implementor;
+  String description;
+  int version;
+  String uuid;
+  String supportedModelArch;
+  int maxSoundModels;
+  int maxKeyPhrases;
+  int maxUsers;
+  int recognitionModes;
+  boolean captureTransition;
+  int maxBufferMs;
+  boolean concurrentCapture;
+  boolean triggerInEvent;
+  int powerConsumptionMw;
+  int audioCapabilities;
+}
diff --git a/media/aidl_api/android.media.soundtrigger.types/current/android/media/soundtrigger/RecognitionConfig.aidl b/media/aidl_api/android.media.soundtrigger.types/current/android/media/soundtrigger/RecognitionConfig.aidl
new file mode 100644
index 0000000..63cd2cbb
--- /dev/null
+++ b/media/aidl_api/android.media.soundtrigger.types/current/android/media/soundtrigger/RecognitionConfig.aidl
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.soundtrigger;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable RecognitionConfig {
+  boolean captureRequested;
+  android.media.soundtrigger.PhraseRecognitionExtra[] phraseRecognitionExtras;
+  int audioCapabilities;
+  byte[] data;
+}
diff --git a/media/aidl_api/android.media.soundtrigger.types/current/android/media/soundtrigger/RecognitionEvent.aidl b/media/aidl_api/android.media.soundtrigger.types/current/android/media/soundtrigger/RecognitionEvent.aidl
new file mode 100644
index 0000000..e6cfb6b
--- /dev/null
+++ b/media/aidl_api/android.media.soundtrigger.types/current/android/media/soundtrigger/RecognitionEvent.aidl
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.soundtrigger;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable RecognitionEvent {
+  android.media.soundtrigger.RecognitionStatus status = android.media.soundtrigger.RecognitionStatus.INVALID;
+  android.media.soundtrigger.SoundModelType type = android.media.soundtrigger.SoundModelType.INVALID;
+  boolean captureAvailable;
+  int captureDelayMs;
+  int capturePreambleMs;
+  boolean triggerInData;
+  @nullable android.media.audio.common.AudioConfig audioConfig;
+  byte[] data;
+}
diff --git a/media/aidl_api/android.media.soundtrigger.types/current/android/media/soundtrigger/RecognitionMode.aidl b/media/aidl_api/android.media.soundtrigger.types/current/android/media/soundtrigger/RecognitionMode.aidl
new file mode 100644
index 0000000..5882910
--- /dev/null
+++ b/media/aidl_api/android.media.soundtrigger.types/current/android/media/soundtrigger/RecognitionMode.aidl
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.soundtrigger;
+/* @hide */
+@Backing(type="int") @VintfStability
+enum RecognitionMode {
+  VOICE_TRIGGER = 1,
+  USER_IDENTIFICATION = 2,
+  USER_AUTHENTICATION = 4,
+  GENERIC_TRIGGER = 8,
+}
diff --git a/media/aidl_api/android.media.soundtrigger.types/current/android/media/soundtrigger/RecognitionStatus.aidl b/media/aidl_api/android.media.soundtrigger.types/current/android/media/soundtrigger/RecognitionStatus.aidl
new file mode 100644
index 0000000..7881c28
--- /dev/null
+++ b/media/aidl_api/android.media.soundtrigger.types/current/android/media/soundtrigger/RecognitionStatus.aidl
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.soundtrigger;
+/* @hide */
+@Backing(type="int") @VintfStability
+enum RecognitionStatus {
+  INVALID = -1,
+  SUCCESS = 0,
+  ABORTED = 1,
+  FAILURE = 2,
+  FORCED = 3,
+}
diff --git a/media/aidl_api/android.media.soundtrigger.types/current/android/media/soundtrigger/SoundModel.aidl b/media/aidl_api/android.media.soundtrigger.types/current/android/media/soundtrigger/SoundModel.aidl
new file mode 100644
index 0000000..fe38264
--- /dev/null
+++ b/media/aidl_api/android.media.soundtrigger.types/current/android/media/soundtrigger/SoundModel.aidl
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.soundtrigger;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable SoundModel {
+  android.media.soundtrigger.SoundModelType type = android.media.soundtrigger.SoundModelType.INVALID;
+  String uuid;
+  String vendorUuid;
+  @nullable ParcelFileDescriptor data;
+  int dataSize;
+}
diff --git a/media/aidl_api/android.media.soundtrigger.types/current/android/media/soundtrigger/SoundModelType.aidl b/media/aidl_api/android.media.soundtrigger.types/current/android/media/soundtrigger/SoundModelType.aidl
new file mode 100644
index 0000000..ac78641
--- /dev/null
+++ b/media/aidl_api/android.media.soundtrigger.types/current/android/media/soundtrigger/SoundModelType.aidl
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.soundtrigger;
+/* @hide */
+@Backing(type="int") @VintfStability
+enum SoundModelType {
+  INVALID = -1,
+  KEYPHRASE = 0,
+  GENERIC = 1,
+}
diff --git a/media/aidl_api/android.media.soundtrigger.types/current/android/media/soundtrigger/Status.aidl b/media/aidl_api/android.media.soundtrigger.types/current/android/media/soundtrigger/Status.aidl
new file mode 100644
index 0000000..29f3167
--- /dev/null
+++ b/media/aidl_api/android.media.soundtrigger.types/current/android/media/soundtrigger/Status.aidl
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.soundtrigger;
+/* @hide */
+@Backing(type="int") @VintfStability
+enum Status {
+  INVALID = -1,
+  SUCCESS = 0,
+  RESOURCE_CONTENTION = 1,
+  OPERATION_NOT_SUPPORTED = 2,
+  TEMPORARY_PERMISSION_DENIED = 3,
+  DEAD_OBJECT = 4,
+  INTERNAL_ERROR = 5,
+}
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index 4968bd1..341b3e9 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -1695,7 +1695,6 @@
     private static final int CB_ERROR = 3;
     private static final int CB_OUTPUT_FORMAT_CHANGE = 4;
 
-
     private class EventHandler extends Handler {
         private MediaCodec mCodec;
 
diff --git a/media/java/android/media/MediaExtractor.java b/media/java/android/media/MediaExtractor.java
index 283f1f1..ae7e5a6 100644
--- a/media/java/android/media/MediaExtractor.java
+++ b/media/java/android/media/MediaExtractor.java
@@ -774,7 +774,7 @@
      */
     public void setLogSessionId(@NonNull LogSessionId logSessionId) {
         mLogSessionId = Objects.requireNonNull(logSessionId);
-        // TODO: implement native_setPlaybackId(playbackId);
+        native_setPlaybackId(logSessionId.getStringId());
     }
 
     /**
@@ -802,6 +802,7 @@
         return bundle;
     }
 
+    private native void native_setPlaybackId(String playbackId);
     private native PersistableBundle native_getMetrics();
 
     private static native final void native_init();
diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java
index 50a326e..5f3a6d7 100644
--- a/media/java/android/media/MediaFormat.java
+++ b/media/java/android/media/MediaFormat.java
@@ -492,6 +492,8 @@
      * <li>The timestampUs shall be monotonically increasing.</li>
      * <li>The timestampUs shall fall within the time span of the video track.</li>
      * <li>The first timestampUs should match that of the first video sample.</li>
+     *
+     * @hide
      */
     public static final String KEY_SLOW_MOTION_MARKERS = "slow-motion-markers";
 
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 2d8babd..db3e8b0 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -20,7 +20,6 @@
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.media.permission.PermissionUtil.myIdentity;
 
-import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -4304,7 +4303,7 @@
     @RequiresPermission(BIND_IMS_SERVICE)
     public void setOnRtpRxNoticeListener(
             @NonNull Context context,
-            @NonNull @CallbackExecutor Executor executor,
+            @NonNull Executor executor,
             @NonNull OnRtpRxNoticeListener listener) {
         Objects.requireNonNull(context);
         Preconditions.checkArgument(
diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
index 5eb57da..6f4e18f 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -168,8 +168,12 @@
     }
 
     /**
+     *
      * Sets the {@link LogSessionId} for MediaRecorder.
      *
+     * <p>The log session ID is a random 32-byte hexadecimal string that is used for monitoring the
+     * MediaRecorder performance.</p>
+     *
      * @param id the global ID for monitoring the MediaRecorder performance
      */
     public void setLogSessionId(@NonNull LogSessionId id) {
diff --git a/media/java/android/media/audiopolicy/AudioProductStrategy.java b/media/java/android/media/audiopolicy/AudioProductStrategy.java
index fca3498..5d6ae54 100644
--- a/media/java/android/media/audiopolicy/AudioProductStrategy.java
+++ b/media/java/android/media/audiopolicy/AudioProductStrategy.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.media.AudioAttributes;
 import android.media.AudioSystem;
 import android.media.MediaRecorder;
@@ -240,6 +241,7 @@
      * @return the legacy stream type relevant for the given {@link AudioAttributes}.
      *         If none is found, it return DEFAULT stream type.
      */
+    @TestApi
     public int getLegacyStreamTypeForAudioAttributes(@NonNull AudioAttributes aa) {
         Preconditions.checkNotNull(aa, "AudioAttributes must not be null");
         for (final AudioAttributesGroup aag : mAudioAttributesGroups) {
@@ -288,6 +290,7 @@
      * @return the volume group id associated with the given audio attributes if found,
      *         {@link AudioVolumeGroup#DEFAULT_VOLUME_GROUP} otherwise.
      */
+    @TestApi
     public int getVolumeGroupIdForAudioAttributes(@NonNull AudioAttributes aa) {
         Preconditions.checkNotNull(aa, "AudioAttributes must not be null");
         for (final AudioAttributesGroup aag : mAudioAttributesGroups) {
diff --git a/media/java/android/media/tv/TvView.java b/media/java/android/media/tv/TvView.java
index 6b329f8..b1baf94 100644
--- a/media/java/android/media/tv/TvView.java
+++ b/media/java/android/media/tv/TvView.java
@@ -23,6 +23,8 @@
 import android.annotation.SystemApi;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
 import android.graphics.Canvas;
 import android.graphics.PorterDuff;
 import android.graphics.Rect;
@@ -39,6 +41,7 @@
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.Pair;
+import android.util.Xml;
 import android.view.InputEvent;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
@@ -99,6 +102,7 @@
     private int mSurfaceWidth;
     private int mSurfaceHeight;
     private final AttributeSet mAttrs;
+    private final XmlResourceParser mParser;
     private final int mDefStyleAttr;
     private int mWindowZOrder;
     private boolean mUseRequestedSurfaceLayout;
@@ -168,7 +172,16 @@
 
     public TvView(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
-        mAttrs = attrs;
+        int sourceResId = Resources.getAttributeSetSourceResId(attrs);
+        if (sourceResId != Resources.ID_NULL) {
+            Log.d(TAG, "Build local AttributeSet");
+            mParser  = context.getResources().getXml(sourceResId);
+            mAttrs = Xml.asAttributeSet(mParser);
+        } else {
+            Log.d(TAG, "Use passed in AttributeSet");
+            mParser = null;
+            mAttrs = attrs;
+        }
         mDefStyleAttr = defStyleAttr;
         resetSurfaceView();
         mTvInputManager = (TvInputManager) getContext().getSystemService(Context.TV_INPUT_SERVICE);
diff --git a/media/java/android/service/media/MediaBrowserService.java b/media/java/android/service/media/MediaBrowserService.java
index ee70714..e8ef464 100644
--- a/media/java/android/service/media/MediaBrowserService.java
+++ b/media/java/android/service/media/MediaBrowserService.java
@@ -46,6 +46,7 @@
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
@@ -112,21 +113,34 @@
     /**
      * All the info about a connection.
      */
-    private class ConnectionRecord implements IBinder.DeathRecipient {
-        String pkg;
-        int uid;
-        int pid;
-        Bundle rootHints;
-        IMediaBrowserServiceCallbacks callbacks;
-        BrowserRoot root;
-        HashMap<String, List<Pair<IBinder, Bundle>>> subscriptions = new HashMap<>();
+    private static class ConnectionRecord implements IBinder.DeathRecipient {
+        public final MediaBrowserService service;
+        public final String pkg;
+        public final int pid;
+        public final int uid;
+        public final Bundle rootHints;
+        public final IMediaBrowserServiceCallbacks callbacks;
+        public final BrowserRoot root;
+        public final HashMap<String, List<Pair<IBinder, Bundle>>> subscriptions = new HashMap<>();
+
+        ConnectionRecord(
+                MediaBrowserService service, String pkg, int pid, int uid, Bundle rootHints,
+                IMediaBrowserServiceCallbacks callbacks, BrowserRoot root) {
+            this.service = service;
+            this.pkg = pkg;
+            this.pid = pid;
+            this.uid = uid;
+            this.rootHints = rootHints;
+            this.callbacks = callbacks;
+            this.root = root;
+        }
 
         @Override
         public void binderDied() {
-            mHandler.post(new Runnable() {
+            service.mHandler.post(new Runnable() {
                 @Override
                 public void run() {
-                    mConnections.remove(callbacks.asBinder());
+                    service.mConnections.remove(callbacks.asBinder());
                 }
             });
         }
@@ -199,39 +213,46 @@
         }
     }
 
-    private class ServiceBinder extends IMediaBrowserService.Stub {
+    private static class ServiceBinder extends IMediaBrowserService.Stub {
+        private WeakReference<MediaBrowserService> mService;
+
+        private ServiceBinder(MediaBrowserService service) {
+            mService = new WeakReference(service);
+        }
+
         @Override
         public void connect(final String pkg, final Bundle rootHints,
                 final IMediaBrowserServiceCallbacks callbacks) {
+            MediaBrowserService service = mService.get();
+            if (service == null) {
+                return;
+            }
 
             final int pid = Binder.getCallingPid();
             final int uid = Binder.getCallingUid();
-            if (!isValidPackage(pkg, uid)) {
+            if (!service.isValidPackage(pkg, uid)) {
                 throw new IllegalArgumentException("Package/uid mismatch: uid=" + uid
                         + " package=" + pkg);
             }
 
-            mHandler.post(new Runnable() {
+            service.mHandler.post(new Runnable() {
                     @Override
                     public void run() {
                         final IBinder b = callbacks.asBinder();
 
                         // Clear out the old subscriptions. We are getting new ones.
-                        mConnections.remove(b);
+                        service.mConnections.remove(b);
 
-                        final ConnectionRecord connection = new ConnectionRecord();
-                        connection.pkg = pkg;
-                        connection.pid = pid;
-                        connection.uid = uid;
-                        connection.rootHints = rootHints;
-                        connection.callbacks = callbacks;
-
-                        mCurConnection = connection;
-                        connection.root = MediaBrowserService.this.onGetRoot(pkg, uid, rootHints);
-                        mCurConnection = null;
+                        // Temporarily sets a placeholder ConnectionRecord to make
+                        // getCurrentBrowserInfo() work in onGetRoot().
+                        service.mCurConnection =
+                                new ConnectionRecord(
+                                        service, pkg, pid, uid, rootHints, callbacks, null);
+                        BrowserRoot root = service.onGetRoot(pkg, uid, rootHints);
+                        service.mCurConnection = null;
 
                         // If they didn't return something, don't allow this client.
-                        if (connection.root == null) {
+                        if (root == null) {
                             Log.i(TAG, "No root for client " + pkg + " from service "
                                     + getClass().getName());
                             try {
@@ -242,16 +263,19 @@
                             }
                         } else {
                             try {
-                                mConnections.put(b, connection);
+                                ConnectionRecord connection =
+                                        new ConnectionRecord(
+                                                service, pkg, pid, uid, rootHints, callbacks, root);
+                                service.mConnections.put(b, connection);
                                 b.linkToDeath(connection, 0);
-                                if (mSession != null) {
+                                if (service.mSession != null) {
                                     callbacks.onConnect(connection.root.getRootId(),
-                                            mSession, connection.root.getExtras());
+                                            service.mSession, connection.root.getExtras());
                                 }
                             } catch (RemoteException ex) {
                                 Log.w(TAG, "Calling onConnect() failed. Dropping client. "
                                         + "pkg=" + pkg);
-                                mConnections.remove(b);
+                                service.mConnections.remove(b);
                             }
                         }
                     }
@@ -260,13 +284,18 @@
 
         @Override
         public void disconnect(final IMediaBrowserServiceCallbacks callbacks) {
-            mHandler.post(new Runnable() {
+            MediaBrowserService service = mService.get();
+            if (service == null) {
+                return;
+            }
+
+            service.mHandler.post(new Runnable() {
                     @Override
                     public void run() {
                         final IBinder b = callbacks.asBinder();
 
                         // Clear out the old subscriptions. We are getting new ones.
-                        final ConnectionRecord old = mConnections.remove(b);
+                        final ConnectionRecord old = service.mConnections.remove(b);
                         if (old != null) {
                             // TODO
                             old.callbacks.asBinder().unlinkToDeath(old, 0);
@@ -283,20 +312,25 @@
         @Override
         public void addSubscription(final String id, final IBinder token, final Bundle options,
                 final IMediaBrowserServiceCallbacks callbacks) {
-            mHandler.post(new Runnable() {
+            MediaBrowserService service = mService.get();
+            if (service == null) {
+                return;
+            }
+
+            service.mHandler.post(new Runnable() {
                     @Override
                     public void run() {
                         final IBinder b = callbacks.asBinder();
 
                         // Get the record for the connection
-                        final ConnectionRecord connection = mConnections.get(b);
+                        final ConnectionRecord connection = service.mConnections.get(b);
                         if (connection == null) {
                             Log.w(TAG, "addSubscription for callback that isn't registered id="
                                     + id);
                             return;
                         }
 
-                        MediaBrowserService.this.addSubscription(id, connection, token, options);
+                        service.addSubscription(id, connection, token, options);
                     }
                 });
         }
@@ -310,18 +344,23 @@
         @Override
         public void removeSubscription(final String id, final IBinder token,
                 final IMediaBrowserServiceCallbacks callbacks) {
-            mHandler.post(new Runnable() {
+            MediaBrowserService service = mService.get();
+            if (service == null) {
+                return;
+            }
+
+            service.mHandler.post(new Runnable() {
                 @Override
                 public void run() {
                     final IBinder b = callbacks.asBinder();
 
-                    ConnectionRecord connection = mConnections.get(b);
+                    ConnectionRecord connection = service.mConnections.get(b);
                     if (connection == null) {
                         Log.w(TAG, "removeSubscription for callback that isn't registered id="
                                 + id);
                         return;
                     }
-                    if (!MediaBrowserService.this.removeSubscription(id, connection, token)) {
+                    if (!service.removeSubscription(id, connection, token)) {
                         Log.w(TAG, "removeSubscription called for " + id
                                 + " which is not subscribed");
                     }
@@ -332,16 +371,21 @@
         @Override
         public void getMediaItem(final String mediaId, final ResultReceiver receiver,
                 final IMediaBrowserServiceCallbacks callbacks) {
-            mHandler.post(new Runnable() {
+            MediaBrowserService service = mService.get();
+            if (service == null) {
+                return;
+            }
+
+            service.mHandler.post(new Runnable() {
                 @Override
                 public void run() {
                     final IBinder b = callbacks.asBinder();
-                    ConnectionRecord connection = mConnections.get(b);
+                    ConnectionRecord connection = service.mConnections.get(b);
                     if (connection == null) {
                         Log.w(TAG, "getMediaItem for callback that isn't registered id=" + mediaId);
                         return;
                     }
-                    performLoadItem(mediaId, connection, receiver);
+                    service.performLoadItem(mediaId, connection, receiver);
                 }
             });
         }
@@ -350,7 +394,7 @@
     @Override
     public void onCreate() {
         super.onCreate();
-        mBinder = new ServiceBinder();
+        mBinder = new ServiceBinder(this);
     }
 
     @Override
diff --git a/media/jni/android_media_MediaExtractor.cpp b/media/jni/android_media_MediaExtractor.cpp
index 6a622c5a..91178e5 100644
--- a/media/jni/android_media_MediaExtractor.cpp
+++ b/media/jni/android_media_MediaExtractor.cpp
@@ -282,7 +282,6 @@
     return status;
 }
 
-
 status_t JMediaExtractor::getSampleMeta(sp<MetaData> *sampleMeta) {
     return mImpl->getSampleMeta(sampleMeta);
 }
@@ -295,6 +294,10 @@
         AudioPresentationCollection *presentations) const {
     return mImpl->getAudioPresentations(trackIdx, presentations);
 }
+
+status_t JMediaExtractor::setPlaybackId(const String8 &playbackId) {
+    return mImpl->setPlaybackId(playbackId);
+}
 }  // namespace android
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -920,6 +923,23 @@
     return mybundle;
 }
 
+static void
+android_media_MediaExtractor_native_setPlaybackId(
+        JNIEnv * env, jobject thiz, jstring playbackIdJString)
+{
+    ALOGV("android_media_MediaExtractor_native_setPlaybackId");
+
+    sp<JMediaExtractor> extractor = getMediaExtractor(env, thiz);
+    if (extractor == nullptr) {
+        jniThrowException(env, "java/lang/IllegalStateException", nullptr);
+    }
+
+    const char* playbackId = env->GetStringUTFChars(playbackIdJString, nullptr);
+    if (extractor->setPlaybackId(String8(playbackId)) != OK) {
+        ALOGE("setPlaybackId failed");
+    }
+    env->ReleaseStringUTFChars(playbackIdJString, playbackId);
+}
 
 static const JNINativeMethod gMethods[] = {
     { "release", "()V", (void *)android_media_MediaExtractor_release },
@@ -990,6 +1010,9 @@
     {"native_getMetrics",          "()Landroid/os/PersistableBundle;",
       (void *)android_media_MediaExtractor_native_getMetrics},
 
+    { "native_setPlaybackId", "(Ljava/lang/String;)V",
+      (void *)android_media_MediaExtractor_native_setPlaybackId},
+
     { "native_getAudioPresentations", "(I)Ljava/util/List;",
       (void *)android_media_MediaExtractor_getAudioPresentations },
 };
diff --git a/media/jni/android_media_MediaExtractor.h b/media/jni/android_media_MediaExtractor.h
index f5ba92e..9aaa643 100644
--- a/media/jni/android_media_MediaExtractor.h
+++ b/media/jni/android_media_MediaExtractor.h
@@ -70,6 +70,8 @@
     status_t getAudioPresentations(size_t trackIdx,
             AudioPresentationCollection *presentations) const;
 
+    status_t setPlaybackId(const String8& playbackId);
+
 protected:
     virtual ~JMediaExtractor();
 
diff --git a/packages/DynamicSystemInstallationService/res/values-fi/strings.xml b/packages/DynamicSystemInstallationService/res/values-fi/strings.xml
index f32fc37..3e474d8 100644
--- a/packages/DynamicSystemInstallationService/res/values-fi/strings.xml
+++ b/packages/DynamicSystemInstallationService/res/values-fi/strings.xml
@@ -7,7 +7,7 @@
     <string name="notification_install_failed" msgid="4066039210317521404">"Asennus epäonnistui"</string>
     <string name="notification_image_validation_failed" msgid="2720357826403917016">"Levykuvan vahvistus epäonnistui. Keskeytä asennus."</string>
     <string name="notification_dynsystem_in_use" msgid="1053194595682188396">"Käyttää tällä hetkellä dynaamista järjestelmää. Käynnistä uudelleen käyttääksesi alkuperäistä Android-versiota."</string>
-    <string name="notification_action_cancel" msgid="5929299408545961077">"Peruuta"</string>
+    <string name="notification_action_cancel" msgid="5929299408545961077">"Peru"</string>
     <string name="notification_action_discard" msgid="1817481003134947493">"Hylkää"</string>
     <string name="notification_action_reboot_to_dynsystem" msgid="4015817159115912479">"Käynn. uudelleen"</string>
     <string name="notification_action_reboot_to_origin" msgid="4013901243271889897">"Käynn. uudelleen"</string>
diff --git a/packages/PrintSpooler/res/values-az/strings.xml b/packages/PrintSpooler/res/values-az/strings.xml
index 4193afc..887434b 100644
--- a/packages/PrintSpooler/res/values-az/strings.xml
+++ b/packages/PrintSpooler/res/values-az/strings.xml
@@ -83,7 +83,7 @@
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> ləğv edilir"</string>
     <string name="failed_notification_title_template" msgid="2256217208186530973">"Printer xətası <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="blocked_notification_title_template" msgid="1175435827331588646">"Printer <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> işini blokladı"</string>
-    <string name="cancel" msgid="4373674107267141885">"Ləğv et"</string>
+    <string name="cancel" msgid="4373674107267141885">"Ləğv edin"</string>
     <string name="restart" msgid="2472034227037808749">"Yenidən başlat"</string>
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Printerə heç bir bağlantı yoxdur"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"naməlum"</string>
diff --git a/packages/PrintSpooler/res/values-fi/strings.xml b/packages/PrintSpooler/res/values-fi/strings.xml
index 724d1d7..4289399 100644
--- a/packages/PrintSpooler/res/values-fi/strings.xml
+++ b/packages/PrintSpooler/res/values-fi/strings.xml
@@ -83,7 +83,7 @@
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Peruutetaan työ <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="failed_notification_title_template" msgid="2256217208186530973">"Tulostinvirhe työlle <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="blocked_notification_title_template" msgid="1175435827331588646">"Tulostin esti työn <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
-    <string name="cancel" msgid="4373674107267141885">"Peruuta"</string>
+    <string name="cancel" msgid="4373674107267141885">"Peru"</string>
     <string name="restart" msgid="2472034227037808749">"Käynnistä uudelleen"</string>
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Ei yhteyttä tulostimeen"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"tuntematon"</string>
diff --git a/packages/PrintSpooler/res/values-ne/strings.xml b/packages/PrintSpooler/res/values-ne/strings.xml
index d0b7a5e4..202653d 100644
--- a/packages/PrintSpooler/res/values-ne/strings.xml
+++ b/packages/PrintSpooler/res/values-ne/strings.xml
@@ -35,7 +35,7 @@
     <string name="install_for_print_preview" msgid="6366303997385509332">"पूर्वावलोकनको लागि PDF भ्यूअर स्थापना गर्नुहोस्"</string>
     <string name="printing_app_crashed" msgid="854477616686566398">"प्रिन्टिङ एप क्र्यास भयो"</string>
     <string name="generating_print_job" msgid="3119608742651698916">"प्रिन्ट कार्य निर्माण गरिँदै"</string>
-    <string name="save_as_pdf" msgid="5718454119847596853">"PDF को रूपमा सुरक्षित गर्नुहोस्"</string>
+    <string name="save_as_pdf" msgid="5718454119847596853">"PDF को रूपमा सेभ गर्नुहोस्"</string>
     <string name="all_printers" msgid="5018829726861876202">"सबै प्रिन्टरहरू..."</string>
     <string name="print_dialog" msgid="32628687461331979">"सम्वाद प्रिन्ट गर्नुहोस्"</string>
     <string name="current_page_template" msgid="5145005201131935302">"<xliff:g id="CURRENT_PAGE">%1$d</xliff:g>/<xliff:g id="PAGE_COUNT">%2$d</xliff:g>"</string>
@@ -44,7 +44,7 @@
     <string name="expand_handle" msgid="7282974448109280522">"ह्यान्डल विस्तार गर्नुहोस्"</string>
     <string name="collapse_handle" msgid="6886637989442507451">"ह्यान्डल कोल्याप्स गर्नुहोस्"</string>
     <string name="print_button" msgid="645164566271246268">"प्रिन्ट गर्नुहोस्"</string>
-    <string name="savetopdf_button" msgid="2976186791686924743">"PDF सुरक्षित गर्नुहोस्"</string>
+    <string name="savetopdf_button" msgid="2976186791686924743">"PDF सेभ गर्नुहोस्"</string>
     <string name="print_options_expanded" msgid="6944679157471691859">"विस्तार गरेका विकल्पहरू प्रिन्ट गर्नुहोस्"</string>
     <string name="print_options_collapsed" msgid="7455930445670414332">"कोल्याप्स गरेका विकल्पहरू प्रिन्ट गर्नुहोस्"</string>
     <string name="search" msgid="5421724265322228497">"खोज्नुहोस्"</string>
diff --git a/packages/SettingsLib/HelpUtils/src/com/android/settingslib/HelpUtils.java b/packages/SettingsLib/HelpUtils/src/com/android/settingslib/HelpUtils.java
index 541a246..281269ec 100644
--- a/packages/SettingsLib/HelpUtils/src/com/android/settingslib/HelpUtils.java
+++ b/packages/SettingsLib/HelpUtils/src/com/android/settingslib/HelpUtils.java
@@ -223,7 +223,7 @@
      *
      * @return the uri with added query parameters
      */
-    private static Uri uriWithAddedParameters(Context context, Uri baseUri) {
+    public static Uri uriWithAddedParameters(Context context, Uri baseUri) {
         Uri.Builder builder = baseUri.buildUpon();
 
         // Add in the preferred language
diff --git a/packages/SettingsLib/res/values-af/strings.xml b/packages/SettingsLib/res/values-af/strings.xml
index 832894c..dff0869 100644
--- a/packages/SettingsLib/res/values-af/strings.xml
+++ b/packages/SettingsLib/res/values-af/strings.xml
@@ -562,8 +562,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"Stel slot op"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"Skakel oor na <xliff:g id="USER_NAME">%s</xliff:g>"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Skep tans nuwe gebruiker …"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"Bynaam"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Voeg gas by"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"Verwyder gas"</string>
diff --git a/packages/SettingsLib/res/values-am/strings.xml b/packages/SettingsLib/res/values-am/strings.xml
index 520289b..4bcbf72 100644
--- a/packages/SettingsLib/res/values-am/strings.xml
+++ b/packages/SettingsLib/res/values-am/strings.xml
@@ -562,8 +562,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"ቁልፍ አዘጋጅ"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"ወደ <xliff:g id="USER_NAME">%s</xliff:g> ቀይር"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"አዲስ ተጠቃሚ በመፍጠር ላይ…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"ቅጽል ስም"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"እንግዳን አክል"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"እንግዳን አስወግድ"</string>
diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml
index af071e8..ccbdbb1 100644
--- a/packages/SettingsLib/res/values-ar/strings.xml
+++ b/packages/SettingsLib/res/values-ar/strings.xml
@@ -508,11 +508,16 @@
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"وقت أقل."</string>
     <string name="cancel" msgid="5665114069455378395">"إلغاء"</string>
     <string name="okay" msgid="949938843324579502">"حسنًا"</string>
-    <string name="alarms_and_reminders_label" msgid="6918395649731424294">"المنبّهات والتذكيرات"</string>
-    <string name="alarms_and_reminders_switch_title" msgid="186992351401152744">"السماح بضبط المنبّهات أو التذكيرات"</string>
-    <string name="alarms_and_reminders_title" msgid="2988400785896875237">"المنبّهات والتذكيرات"</string>
-    <string name="alarms_and_reminders_footer_title" msgid="5882788882647778753">"يمكنك السماح لهذا التطبيق بضبط المنبّهات أو الموقّتات الأخرى استنادًا إلى الأحداث. سيسمح هذا الأذن بتنشيط التطبيق وتشغيله حتى في حال عدم استخدام الجهاز. تجدر الإشارة إلى أن إبطال هذا الأذن قد يسبب خللاً في عمل التطبيق، وعلى وجه الخصوص لن تعمل أي منبّهات تم ضبطها بواسطة التطبيق."</string>
-    <string name="keywords_alarms_and_reminders" msgid="8882739572152019456">"جدول زمني، منبّه، تذكير، حدث، فعالية"</string>
+    <!-- no translation found for alarms_and_reminders_label (6918395649731424294) -->
+    <skip />
+    <!-- no translation found for alarms_and_reminders_switch_title (186992351401152744) -->
+    <skip />
+    <!-- no translation found for alarms_and_reminders_title (2988400785896875237) -->
+    <skip />
+    <!-- no translation found for alarms_and_reminders_footer_title (5882788882647778753) -->
+    <skip />
+    <!-- no translation found for keywords_alarms_and_reminders (8882739572152019456) -->
+    <skip />
     <string name="zen_mode_enable_dialog_turn_on" msgid="6418297231575050426">"تفعيل"</string>
     <string name="zen_mode_settings_turn_on_dialog_title" msgid="2760567063190790696">"تفعيل وضع \"الرجاء عدم الإزعاج\""</string>
     <string name="zen_mode_settings_summary_off" msgid="3832876036123504076">"مطلقًا"</string>
@@ -566,8 +571,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"تعيين التأمين"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"التبديل إلى <xliff:g id="USER_NAME">%s</xliff:g>"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"جارٍ إنشاء مستخدم جديد…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"اللقب"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"إضافة ضيف"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"إزالة جلسة الضيف"</string>
diff --git a/packages/SettingsLib/res/values-as/strings.xml b/packages/SettingsLib/res/values-as/strings.xml
index 8b6d29b..e5b30cb 100644
--- a/packages/SettingsLib/res/values-as/strings.xml
+++ b/packages/SettingsLib/res/values-as/strings.xml
@@ -562,8 +562,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"লক ছেট কৰক"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g>লৈ সলনি কৰক"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"নতুন ব্যৱহাৰকাৰী সৃষ্টি কৰি থকা হৈছে…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"উপনাম"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"অতিথি যোগ কৰক"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"অতিথি আঁতৰাওক"</string>
diff --git a/packages/SettingsLib/res/values-az/strings.xml b/packages/SettingsLib/res/values-az/strings.xml
index f00a87b..b58c3e2 100644
--- a/packages/SettingsLib/res/values-az/strings.xml
+++ b/packages/SettingsLib/res/values-az/strings.xml
@@ -562,8 +562,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"Kilid ayarlayın"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g> adlı istifadəçiyə keçin"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Yeni istifadəçi yaradılır…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"Ləqəb"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Qonaq əlavə edin"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"Qonağı silin"</string>
diff --git a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
index 26528b5..4fe132d 100644
--- a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
+++ b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
@@ -563,8 +563,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"Podesi zaključavanje"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"Pređi na korisnika <xliff:g id="USER_NAME">%s</xliff:g>"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Pravi se novi korisnik…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"Nadimak"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Dodaj gosta"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"Ukloni gosta"</string>
diff --git a/packages/SettingsLib/res/values-be/strings.xml b/packages/SettingsLib/res/values-be/strings.xml
index 3f83d71..268f287 100644
--- a/packages/SettingsLib/res/values-be/strings.xml
+++ b/packages/SettingsLib/res/values-be/strings.xml
@@ -564,8 +564,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"Усталёўка блакiроўкi"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"Пераключыцца на карыстальніка <xliff:g id="USER_NAME">%s</xliff:g>"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Ствараецца новы карыстальнік…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"Псеўданім"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Дадаць госця"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"Выдаліць госця"</string>
diff --git a/packages/SettingsLib/res/values-bg/strings.xml b/packages/SettingsLib/res/values-bg/strings.xml
index 55e8732..25536e2 100644
--- a/packages/SettingsLib/res/values-bg/strings.xml
+++ b/packages/SettingsLib/res/values-bg/strings.xml
@@ -450,8 +450,10 @@
     <string name="power_remaining_duration_shutdown_imminent" product="tablet" msgid="7703677921000858479">"Възможно е таблетът да се изключи скоро (<xliff:g id="LEVEL">%1$s</xliff:g>)"</string>
     <string name="power_remaining_duration_shutdown_imminent" product="device" msgid="4374784375644214578">"Възможно е устройството да се изключи скоро (<xliff:g id="LEVEL">%1$s</xliff:g>)"</string>
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string>
-    <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Оставащо време до пълно зареждане: <xliff:g id="TIME">%1$s</xliff:g>"</string>
-    <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – Оставащо време до пълно зареждане: <xliff:g id="TIME">%2$s</xliff:g>"</string>
+    <!-- no translation found for power_remaining_charging_duration_only (8085099012811384899) -->
+    <skip />
+    <!-- no translation found for power_charging_duration (6127154952524919719) -->
+    <skip />
     <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> – Оптимизиране с цел състоянието на батерията"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Неизвестно"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Зарежда се"</string>
@@ -459,7 +461,8 @@
     <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"Зарежда се бавно"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Не се зарежда"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Включена в захранването, в момента не се зарежда"</string>
-    <string name="battery_info_status_full" msgid="1339002294876531312">"Заредена"</string>
+    <!-- no translation found for battery_info_status_full (1339002294876531312) -->
+    <skip />
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Контролира се от администратор"</string>
     <string name="disabled" msgid="8017887509554714950">"Деактивирано"</string>
     <string name="external_source_trusted" msgid="1146522036773132905">"Има разрешение"</string>
@@ -559,8 +562,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"Задаване на заключване"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"Превключване към <xliff:g id="USER_NAME">%s</xliff:g>"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Създава се нов потребител…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"Псевдоним"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Добавяне на гост"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"Премахване на госта"</string>
diff --git a/packages/SettingsLib/res/values-bn/strings.xml b/packages/SettingsLib/res/values-bn/strings.xml
index 9e2ce090..ad4bf64 100644
--- a/packages/SettingsLib/res/values-bn/strings.xml
+++ b/packages/SettingsLib/res/values-bn/strings.xml
@@ -504,11 +504,16 @@
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"আরও কম।"</string>
     <string name="cancel" msgid="5665114069455378395">"বাতিল"</string>
     <string name="okay" msgid="949938843324579502">"ঠিক আছে"</string>
-    <string name="alarms_and_reminders_label" msgid="6918395649731424294">"অ্যালার্ম এবং রিমাইন্ডার"</string>
-    <string name="alarms_and_reminders_switch_title" msgid="186992351401152744">"অ্যালার্ম এবং রিমাইন্ডার সেট করতে অনুমতি দিন"</string>
-    <string name="alarms_and_reminders_title" msgid="2988400785896875237">"অ্যালার্ম এবং রিমাইন্ডার"</string>
-    <string name="alarms_and_reminders_footer_title" msgid="5882788882647778753">"অ্যালার্ম শিডিউল করা বা অন্য সময় ভিত্তিক ইভেন্টের জন্য এই অ্যাপকে অনুমতি দিন। আপনি ডিভাইস ব্যবহার না করলেও এর ফলে অ্যাপ কাজ করা শুরু করে দেবে। মনে রাখবেন অনুমতি না দিলে অ্যাপ ঠিকভাবে কাজ নাও করতে পারে, বিশেষ করে অ্যাপে শিডিউল করা যেকোনও অ্যালার্ম নাও বাজতে পারে।"</string>
-    <string name="keywords_alarms_and_reminders" msgid="8882739572152019456">"শিডিউল, অ্যালার্ম, রিমাইন্ডার, ইভেন্ট"</string>
+    <!-- no translation found for alarms_and_reminders_label (6918395649731424294) -->
+    <skip />
+    <!-- no translation found for alarms_and_reminders_switch_title (186992351401152744) -->
+    <skip />
+    <!-- no translation found for alarms_and_reminders_title (2988400785896875237) -->
+    <skip />
+    <!-- no translation found for alarms_and_reminders_footer_title (5882788882647778753) -->
+    <skip />
+    <!-- no translation found for keywords_alarms_and_reminders (8882739572152019456) -->
+    <skip />
     <string name="zen_mode_enable_dialog_turn_on" msgid="6418297231575050426">"চালু করুন"</string>
     <string name="zen_mode_settings_turn_on_dialog_title" msgid="2760567063190790696">"\'বিরক্ত করবে না\' মোড চালু করুন"</string>
     <string name="zen_mode_settings_summary_off" msgid="3832876036123504076">"কখনও নয়"</string>
@@ -562,8 +567,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"লক সেট করুন"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g>-এ পাল্টান"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"নতুন ব্যবহারকারী তৈরি করা হচ্ছে…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"বিশেষ নাম"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"অতিথি যোগ করুন"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"অতিথি সরান"</string>
diff --git a/packages/SettingsLib/res/values-bs/strings.xml b/packages/SettingsLib/res/values-bs/strings.xml
index 1021464..f56b66e 100644
--- a/packages/SettingsLib/res/values-bs/strings.xml
+++ b/packages/SettingsLib/res/values-bs/strings.xml
@@ -563,8 +563,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"Postaviti zaključavanje"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"Prebaci na korisnika <xliff:g id="USER_NAME">%s</xliff:g>"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Kreiranje novog korisnika…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"Nadimak"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Dodaj gosta"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"Ukloni gosta"</string>
diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml
index 2445571..36c23c9 100644
--- a/packages/SettingsLib/res/values-ca/strings.xml
+++ b/packages/SettingsLib/res/values-ca/strings.xml
@@ -562,8 +562,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"Defineix un bloqueig"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"Canvia a <xliff:g id="USER_NAME">%s</xliff:g>"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"S\'està creant l\'usuari…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"Àlies"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Afegeix un convidat"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"Suprimeix el convidat"</string>
diff --git a/packages/SettingsLib/res/values-cs/strings.xml b/packages/SettingsLib/res/values-cs/strings.xml
index 1e38693..2466b9c 100644
--- a/packages/SettingsLib/res/values-cs/strings.xml
+++ b/packages/SettingsLib/res/values-cs/strings.xml
@@ -564,8 +564,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"Nastavit zámek"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"Přepnout na uživatele <xliff:g id="USER_NAME">%s</xliff:g>"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Vytváření nového uživatele…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"Přezdívka"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Přidat hosta"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"Odstranit hosta"</string>
diff --git a/packages/SettingsLib/res/values-da/strings.xml b/packages/SettingsLib/res/values-da/strings.xml
index f1ea6cc..5c46619 100644
--- a/packages/SettingsLib/res/values-da/strings.xml
+++ b/packages/SettingsLib/res/values-da/strings.xml
@@ -559,8 +559,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"Konfigurer låseskærmen"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"Skift til <xliff:g id="USER_NAME">%s</xliff:g>"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Opretter ny bruger…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"Kaldenavn"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Tilføj gæst"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"Fjern gæsten"</string>
diff --git a/packages/SettingsLib/res/values-de/strings.xml b/packages/SettingsLib/res/values-de/strings.xml
index dcf7879..99aad671 100644
--- a/packages/SettingsLib/res/values-de/strings.xml
+++ b/packages/SettingsLib/res/values-de/strings.xml
@@ -559,8 +559,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"Sperre einrichten"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"Zu <xliff:g id="USER_NAME">%s</xliff:g> wechseln"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Neuer Nutzer wird erstellt…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"Alias"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Gast hinzufügen"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"Gast entfernen"</string>
diff --git a/packages/SettingsLib/res/values-el/strings.xml b/packages/SettingsLib/res/values-el/strings.xml
index 9ebafd9..878907e 100644
--- a/packages/SettingsLib/res/values-el/strings.xml
+++ b/packages/SettingsLib/res/values-el/strings.xml
@@ -562,8 +562,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"Ορισμός κλειδώματος"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"Εναλλαγή σε <xliff:g id="USER_NAME">%s</xliff:g>"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Δημιουργία νέου χρήστη…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"Ψευδώνυμο"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Προσθήκη επισκέπτη"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"Κατάργηση επισκέπτη"</string>
diff --git a/packages/SettingsLib/res/values-en-rAU/strings.xml b/packages/SettingsLib/res/values-en-rAU/strings.xml
index 7beb3789..e70d99a 100644
--- a/packages/SettingsLib/res/values-en-rAU/strings.xml
+++ b/packages/SettingsLib/res/values-en-rAU/strings.xml
@@ -559,7 +559,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"Set lock"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"Switch to <xliff:g id="USER_NAME">%s</xliff:g>"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Creating new user…"</string>
-    <string name="add_user_failed" msgid="4809887794313944872">"Failed to create a new user"</string>
     <string name="user_nickname" msgid="262624187455825083">"Nickname"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Add guest"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"Remove guest"</string>
diff --git a/packages/SettingsLib/res/values-en-rCA/strings.xml b/packages/SettingsLib/res/values-en-rCA/strings.xml
index b1596c3..437e49a 100644
--- a/packages/SettingsLib/res/values-en-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-en-rCA/strings.xml
@@ -559,7 +559,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"Set lock"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"Switch to <xliff:g id="USER_NAME">%s</xliff:g>"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Creating new user…"</string>
-    <string name="add_user_failed" msgid="4809887794313944872">"Failed to create a new user"</string>
     <string name="user_nickname" msgid="262624187455825083">"Nickname"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Add guest"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"Remove guest"</string>
diff --git a/packages/SettingsLib/res/values-en-rGB/strings.xml b/packages/SettingsLib/res/values-en-rGB/strings.xml
index 7beb3789..e70d99a 100644
--- a/packages/SettingsLib/res/values-en-rGB/strings.xml
+++ b/packages/SettingsLib/res/values-en-rGB/strings.xml
@@ -559,7 +559,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"Set lock"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"Switch to <xliff:g id="USER_NAME">%s</xliff:g>"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Creating new user…"</string>
-    <string name="add_user_failed" msgid="4809887794313944872">"Failed to create a new user"</string>
     <string name="user_nickname" msgid="262624187455825083">"Nickname"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Add guest"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"Remove guest"</string>
diff --git a/packages/SettingsLib/res/values-en-rIN/strings.xml b/packages/SettingsLib/res/values-en-rIN/strings.xml
index 7beb3789..e70d99a 100644
--- a/packages/SettingsLib/res/values-en-rIN/strings.xml
+++ b/packages/SettingsLib/res/values-en-rIN/strings.xml
@@ -559,7 +559,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"Set lock"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"Switch to <xliff:g id="USER_NAME">%s</xliff:g>"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Creating new user…"</string>
-    <string name="add_user_failed" msgid="4809887794313944872">"Failed to create a new user"</string>
     <string name="user_nickname" msgid="262624187455825083">"Nickname"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Add guest"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"Remove guest"</string>
diff --git a/packages/SettingsLib/res/values-en-rXC/strings.xml b/packages/SettingsLib/res/values-en-rXC/strings.xml
index c984f6e..fc4f3b9 100644
--- a/packages/SettingsLib/res/values-en-rXC/strings.xml
+++ b/packages/SettingsLib/res/values-en-rXC/strings.xml
@@ -559,7 +559,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‎‎‏‏‏‏‎‎‏‏‏‎‎‎‏‎‏‏‎‏‏‎‎‎‎‏‎‏‏‏‏‎‎‎‏‎‏‏‎‏‎‏‎‏‏‏‏‎‎‏‎‏‎‎‏‎‏‎‎‎‎Set lock‎‏‎‎‏‎"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‎‎‎‎‏‏‎‎‏‏‎‏‏‎‏‏‎‎‏‏‎‎‎‏‎‏‎‎‎‎‏‏‎‎‎‎‎‎‏‎‎‎‎‏‎‎‏‏‏‎‏‏‏‏‎‏‏‏‏‏‎Switch to ‎‏‎‎‏‏‎<xliff:g id="USER_NAME">%s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‎‏‎‎‎‏‏‎‎‎‎‎‎‏‎‏‏‎‏‎‎‏‎‎‎‏‎‏‏‏‎‏‎‏‎‏‏‎‎‎‏‎‏‏‏‏‎‏‎‎‏‏‎‎‎‎‎‏‏‏‎Creating new user…‎‏‎‎‏‎"</string>
-    <string name="add_user_failed" msgid="4809887794313944872">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‎‎‏‎‏‏‎‎‎‎‎‎‎‎‏‎‎‏‏‏‎‏‏‏‎‏‏‏‎‎‎‎‏‏‎‏‏‎‎‏‎‎‎‏‏‏‎‏‏‏‏‏‎‎‏‎‏‎‎‎‎Failed to create a new user‎‏‎‎‏‎"</string>
     <string name="user_nickname" msgid="262624187455825083">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‏‎‏‏‏‎‏‎‎‏‎‏‎‎‎‎‎‏‏‏‎‏‎‎‏‏‏‎‏‎‎‏‏‎‏‎‏‎‏‏‏‎‏‎‏‏‏‎‎‏‎‎‏‎‏‏‏‎‏‏‎Nickname‎‏‎‎‏‎"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‎‎‎‎‎‏‎‏‎‎‏‎‏‎‏‎‎‏‎‎‎‏‎‎‏‏‏‏‎‏‏‎‏‏‎‎‎‎‎‎‎‏‏‏‏‏‏‎‎‏‏‎‏‏‏‎‎‏‎‏‎Add guest‎‏‎‎‏‎"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‏‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‏‎‎‎‏‎‎‏‎‏‎‏‏‏‎‏‎‏‎‎‏‎‎‏‏‎‎‎‎‎‎‏‏‎‎‏‎‎‏‏‏‎‎‎‎Remove guest‎‏‎‎‏‎"</string>
diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml
index 0b543b8..750a048 100644
--- a/packages/SettingsLib/res/values-es-rUS/strings.xml
+++ b/packages/SettingsLib/res/values-es-rUS/strings.xml
@@ -450,8 +450,10 @@
     <string name="power_remaining_duration_shutdown_imminent" product="tablet" msgid="7703677921000858479">"Es posible que pronto se apague la tablet (<xliff:g id="LEVEL">%1$s</xliff:g>)"</string>
     <string name="power_remaining_duration_shutdown_imminent" product="device" msgid="4374784375644214578">"Es posible que pronto se apague el dispositivo (<xliff:g id="LEVEL">%1$s</xliff:g>)"</string>
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g>: <xliff:g id="STATE">%2$s</xliff:g>"</string>
-    <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> para completar"</string>
-    <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> para completar"</string>
+    <!-- no translation found for power_remaining_charging_duration_only (8085099012811384899) -->
+    <skip />
+    <!-- no translation found for power_charging_duration (6127154952524919719) -->
+    <skip />
     <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g>: Optimizando el estado de la batería"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Desconocido"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Cargando"</string>
@@ -459,7 +461,8 @@
     <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"Carga lenta"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"No se está cargando."</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Conectado. No se puede cargar en este momento"</string>
-    <string name="battery_info_status_full" msgid="1339002294876531312">"Cargada"</string>
+    <!-- no translation found for battery_info_status_full (1339002294876531312) -->
+    <skip />
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Controlada por el administrador"</string>
     <string name="disabled" msgid="8017887509554714950">"Inhabilitada"</string>
     <string name="external_source_trusted" msgid="1146522036773132905">"Con permiso"</string>
@@ -559,8 +562,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"Configurar bloqueo"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"Cambiar a <xliff:g id="USER_NAME">%s</xliff:g>"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Creando usuario nuevo…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"Sobrenombre"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Agregar invitado"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"Quitar invitado"</string>
diff --git a/packages/SettingsLib/res/values-es/strings.xml b/packages/SettingsLib/res/values-es/strings.xml
index 5d738d7..ff1582e 100644
--- a/packages/SettingsLib/res/values-es/strings.xml
+++ b/packages/SettingsLib/res/values-es/strings.xml
@@ -562,8 +562,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"Establecer bloqueo"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"Cambiar a <xliff:g id="USER_NAME">%s</xliff:g>"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Creando usuario…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"Apodo"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Añadir invitado"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"Quitar invitado"</string>
diff --git a/packages/SettingsLib/res/values-et/strings.xml b/packages/SettingsLib/res/values-et/strings.xml
index 591290b..1e6ad9f 100644
--- a/packages/SettingsLib/res/values-et/strings.xml
+++ b/packages/SettingsLib/res/values-et/strings.xml
@@ -562,8 +562,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"Määra lukk"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"Lülita kasutajale <xliff:g id="USER_NAME">%s</xliff:g>"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Uue kasutaja loomine …"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"Hüüdnimi"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Lisa külaline"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"Eemalda külaline"</string>
diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml
index f5c3615..a77ba2d 100644
--- a/packages/SettingsLib/res/values-eu/strings.xml
+++ b/packages/SettingsLib/res/values-eu/strings.xml
@@ -562,8 +562,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"Ezarri blokeoa"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"Aldatu <xliff:g id="USER_NAME">%s</xliff:g> erabiltzailera"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Beste erabiltzaile bat sortzen…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"Goitizena"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Gehitu gonbidatua"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"Kendu gonbidatua"</string>
diff --git a/packages/SettingsLib/res/values-fa/strings.xml b/packages/SettingsLib/res/values-fa/strings.xml
index a4cceba..a8ed0d0 100644
--- a/packages/SettingsLib/res/values-fa/strings.xml
+++ b/packages/SettingsLib/res/values-fa/strings.xml
@@ -562,8 +562,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"تنظیم قفل"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"رفتن به <xliff:g id="USER_NAME">%s</xliff:g>"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"درحال ایجاد کاربر جدید…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"نام مستعار"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"افزودن مهمان"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"حذف مهمان"</string>
diff --git a/packages/SettingsLib/res/values-fi/strings.xml b/packages/SettingsLib/res/values-fi/strings.xml
index f6a1f41..b545dee 100644
--- a/packages/SettingsLib/res/values-fi/strings.xml
+++ b/packages/SettingsLib/res/values-fi/strings.xml
@@ -562,8 +562,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"Aseta lukitus"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"Vaihda tähän käyttäjään: <xliff:g id="USER_NAME">%s</xliff:g>"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Luodaan uutta käyttäjää…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"Lempinimi"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Lisää vieras"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"Poista vieras"</string>
diff --git a/packages/SettingsLib/res/values-fr-rCA/strings.xml b/packages/SettingsLib/res/values-fr-rCA/strings.xml
index 2b379c7..21ddbd1 100644
--- a/packages/SettingsLib/res/values-fr-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-fr-rCA/strings.xml
@@ -562,8 +562,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"Définir verrouillage écran"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"Passer à <xliff:g id="USER_NAME">%s</xliff:g>"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Créer un utilisateur…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"Pseudo"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Ajouter un invité"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"Supprimer l\'invité"</string>
diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml
index ef4c707..d899fb0 100644
--- a/packages/SettingsLib/res/values-fr/strings.xml
+++ b/packages/SettingsLib/res/values-fr/strings.xml
@@ -562,8 +562,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"Définir verrouillage écran"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"Passer à <xliff:g id="USER_NAME">%s</xliff:g>"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Création d\'un nouvel utilisateur…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"Pseudo"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Ajouter un invité"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"Supprimer l\'invité"</string>
diff --git a/packages/SettingsLib/res/values-gl/strings.xml b/packages/SettingsLib/res/values-gl/strings.xml
index e9cfa40..747370c 100644
--- a/packages/SettingsLib/res/values-gl/strings.xml
+++ b/packages/SettingsLib/res/values-gl/strings.xml
@@ -562,8 +562,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"Establecer bloqueo"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"Cambiar a <xliff:g id="USER_NAME">%s</xliff:g>"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Creando usuario novo…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"Alcume"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Engadir convidado"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"Quitar convidado"</string>
diff --git a/packages/SettingsLib/res/values-gu/strings.xml b/packages/SettingsLib/res/values-gu/strings.xml
index 378215c..7601720 100644
--- a/packages/SettingsLib/res/values-gu/strings.xml
+++ b/packages/SettingsLib/res/values-gu/strings.xml
@@ -562,8 +562,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"લૉક સેટ કરો"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g> પર સ્વિચ કરો"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"નવા વપરાશકર્તા બનાવી રહ્યાં છીએ…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"ઉપનામ"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"અતિથિ ઉમેરો"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"અતિથિને કાઢી નાખો"</string>
diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml
index 319150a..6dc6a7e 100644
--- a/packages/SettingsLib/res/values-hi/strings.xml
+++ b/packages/SettingsLib/res/values-hi/strings.xml
@@ -562,8 +562,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"लॉक सेट करें"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g> पर जाएं"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"नया उपयोगकर्ता बनाया जा रहा है…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"प्रचलित नाम"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"मेहमान जोड़ें"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"मेहमान हटाएं"</string>
diff --git a/packages/SettingsLib/res/values-hr/strings.xml b/packages/SettingsLib/res/values-hr/strings.xml
index 82bd8ae..7aefec3 100644
--- a/packages/SettingsLib/res/values-hr/strings.xml
+++ b/packages/SettingsLib/res/values-hr/strings.xml
@@ -563,8 +563,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"Postavi zaključavanje"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"Prelazak na korisnika <xliff:g id="USER_NAME">%s</xliff:g>"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Izrada novog korisnika…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"Nadimak"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Dodavanje gosta"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"Uklanjanje gosta"</string>
diff --git a/packages/SettingsLib/res/values-hu/strings.xml b/packages/SettingsLib/res/values-hu/strings.xml
index edea140..fe4aa3a 100644
--- a/packages/SettingsLib/res/values-hu/strings.xml
+++ b/packages/SettingsLib/res/values-hu/strings.xml
@@ -562,8 +562,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"Képernyőzár beállítása"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"Váltás erre: <xliff:g id="USER_NAME">%s</xliff:g>"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Új felhasználó létrehozása…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"Becenév"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Vendég hozzáadása"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"Vendég munkamenet eltávolítása"</string>
diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml
index 4a7b0fe..3b6b91d 100644
--- a/packages/SettingsLib/res/values-hy/strings.xml
+++ b/packages/SettingsLib/res/values-hy/strings.xml
@@ -507,7 +507,7 @@
     <string name="alarms_and_reminders_label" msgid="6918395649731424294">"Զարթուցիչներ և հիշեցումներ"</string>
     <string name="alarms_and_reminders_switch_title" msgid="186992351401152744">"Թույլատրել կարգավորել զարթուցիչներ և հիշեցումներ"</string>
     <string name="alarms_and_reminders_title" msgid="2988400785896875237">"Զարթուցիչներ և հիշեցումներ"</string>
-    <string name="alarms_and_reminders_footer_title" msgid="5882788882647778753">"Թույլ տվեք այս հավելվածին կարգավորել զարթուցիչներ և ժամանակացույցների հետ կապված այլ իրադարձություններ։ Հավելվածը կկարողանա միանալ և գործարկվել, նույնիսկ եթե չեք օգտագործում սարքը։ Նկատի ունեցեք, որ եթե չեղարկեք այս թույլտվությունը, հավելվածը կարող է աշխատել թերություններով, մասնավորապես, հավելվածի կողմից կարգավորված զարթուցիչներն այլևս չեն աշխատի։"</string>
+    <string name="alarms_and_reminders_footer_title" msgid="5882788882647778753">"Թույլատրեք այս հավելվածին կարգավորել զարթուցիչներ և ժամանակացույցների հետ կապված այլ իրադարձություններ։ Դա թույլ կտա հավելվածին միանալ և գործարկվել, նույնիսկ եթե չեք օգտագործում սարքը։ Նկատի ունեցեք, որ եթե չեղարկեք այս թույլտվությունը, հավելվածը կարող է աշխատել թերություններով, մասնավորապես, հավելվածի կողմից կարգավորված զարթուցիչներն այլևս չեն աշխատի։"</string>
     <string name="keywords_alarms_and_reminders" msgid="8882739572152019456">"ժամանակացույց, զարթուցիչ, հիշեցում, իրադարձություն"</string>
     <string name="zen_mode_enable_dialog_turn_on" msgid="6418297231575050426">"Միացնել"</string>
     <string name="zen_mode_settings_turn_on_dialog_title" msgid="2760567063190790696">"Միացրեք «Չանհանգստացնել» ռեժիմը"</string>
@@ -562,8 +562,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"Կարգավորել կողպումը"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"Անցնել <xliff:g id="USER_NAME">%s</xliff:g> պրոֆիլին"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Ստեղծվում է օգտատիրոջ նոր պրոֆիլ…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"Կեղծանուն"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Ավելացնել հյուր"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"Հեռացնել հյուրին"</string>
diff --git a/packages/SettingsLib/res/values-in/strings.xml b/packages/SettingsLib/res/values-in/strings.xml
index 6c70022..d084998 100644
--- a/packages/SettingsLib/res/values-in/strings.xml
+++ b/packages/SettingsLib/res/values-in/strings.xml
@@ -562,8 +562,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"Setel kunci"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"Beralih ke <xliff:g id="USER_NAME">%s</xliff:g>"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Membuat pengguna baru …"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"Nama panggilan"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Tambahkan tamu"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"Hapus tamu"</string>
diff --git a/packages/SettingsLib/res/values-is/strings.xml b/packages/SettingsLib/res/values-is/strings.xml
index 541840ad..ddf0742 100644
--- a/packages/SettingsLib/res/values-is/strings.xml
+++ b/packages/SettingsLib/res/values-is/strings.xml
@@ -562,8 +562,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"Velja lás"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"Skipta yfir í <xliff:g id="USER_NAME">%s</xliff:g>"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Stofnar nýjan notanda…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"Gælunafn"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Bæta gesti við"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"Fjarlægja gest"</string>
diff --git a/packages/SettingsLib/res/values-it/strings.xml b/packages/SettingsLib/res/values-it/strings.xml
index 58c0012..7bf9eba 100644
--- a/packages/SettingsLib/res/values-it/strings.xml
+++ b/packages/SettingsLib/res/values-it/strings.xml
@@ -559,8 +559,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"Imposta blocco"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"Passa a <xliff:g id="USER_NAME">%s</xliff:g>"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Creazione nuovo utente…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"Nickname"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Aggiungi ospite"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"Rimuovi ospite"</string>
diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml
index 8ab65d6..87f59c2 100644
--- a/packages/SettingsLib/res/values-iw/strings.xml
+++ b/packages/SettingsLib/res/values-iw/strings.xml
@@ -564,8 +564,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"הגדרת נעילה"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"מעבר אל <xliff:g id="USER_NAME">%s</xliff:g>"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"בתהליך יצירה של משתמש חדש…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"כינוי"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"הוספת אורח"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"הסרת אורח"</string>
diff --git a/packages/SettingsLib/res/values-ja/strings.xml b/packages/SettingsLib/res/values-ja/strings.xml
index 19efad6..c9d58e9 100644
--- a/packages/SettingsLib/res/values-ja/strings.xml
+++ b/packages/SettingsLib/res/values-ja/strings.xml
@@ -562,8 +562,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"ロックを設定"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g> に切り替え"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"新しいユーザーを作成しています…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"ニックネーム"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"ゲストを追加"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"ゲストを削除"</string>
diff --git a/packages/SettingsLib/res/values-ka/strings.xml b/packages/SettingsLib/res/values-ka/strings.xml
index ef1110e..bc12f94 100644
--- a/packages/SettingsLib/res/values-ka/strings.xml
+++ b/packages/SettingsLib/res/values-ka/strings.xml
@@ -562,8 +562,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"საკეტის დაყენება"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g>-ზე გადართვა"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"მიმდინარეობს ახალი მომხმარებლის შექმნა…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"მეტსახელი"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"სტუმრის დამატება"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"სტუმრის ამოშლა"</string>
diff --git a/packages/SettingsLib/res/values-kk/strings.xml b/packages/SettingsLib/res/values-kk/strings.xml
index 0a9db64..393a55f 100644
--- a/packages/SettingsLib/res/values-kk/strings.xml
+++ b/packages/SettingsLib/res/values-kk/strings.xml
@@ -562,8 +562,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"Бекітпе тағайындау"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g> пайдаланушысына ауысу"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Жаңа пайдаланушы профилі жасалуда…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"Лақап ат"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Қонақ қосу"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"Қонақты жою"</string>
diff --git a/packages/SettingsLib/res/values-km/strings.xml b/packages/SettingsLib/res/values-km/strings.xml
index 3635c82..605b3be 100644
--- a/packages/SettingsLib/res/values-km/strings.xml
+++ b/packages/SettingsLib/res/values-km/strings.xml
@@ -562,8 +562,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"កំណត់​ការ​ចាក់​សោ"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"ប្ដូរទៅ <xliff:g id="USER_NAME">%s</xliff:g>"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"កំពុងបង្កើត​អ្នកប្រើប្រាស់ថ្មី…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"ឈ្មោះ​ហៅក្រៅ"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"បញ្ចូល​ភ្ញៀវ"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"ដកភ្ញៀវចេញ"</string>
diff --git a/packages/SettingsLib/res/values-kn/strings.xml b/packages/SettingsLib/res/values-kn/strings.xml
index a261d21..4054024 100644
--- a/packages/SettingsLib/res/values-kn/strings.xml
+++ b/packages/SettingsLib/res/values-kn/strings.xml
@@ -562,8 +562,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"ಲಾಕ್ ಹೊಂದಿಸಿ"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g> ಗೆ ಬದಲಿಸಿ"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"ಹೊಸ ಬಳಕೆದಾರರನ್ನು ರಚಿಸಲಾಗುತ್ತಿದೆ…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"ಅಡ್ಡ ಹೆಸರು"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"ಅತಿಥಿಯನ್ನು ಸೇರಿಸಿ"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"ಅತಿಥಿಯನ್ನು ತೆಗೆದುಹಾಕಿ"</string>
diff --git a/packages/SettingsLib/res/values-ko/strings.xml b/packages/SettingsLib/res/values-ko/strings.xml
index 14d5d1c..d4719ba 100644
--- a/packages/SettingsLib/res/values-ko/strings.xml
+++ b/packages/SettingsLib/res/values-ko/strings.xml
@@ -562,8 +562,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"잠금 설정"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g>(으)로 전환"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"새로운 사용자를 만드는 중…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"닉네임"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"게스트 추가"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"게스트 삭제"</string>
diff --git a/packages/SettingsLib/res/values-ky/strings.xml b/packages/SettingsLib/res/values-ky/strings.xml
index a52cad6..c0dab06 100644
--- a/packages/SettingsLib/res/values-ky/strings.xml
+++ b/packages/SettingsLib/res/values-ky/strings.xml
@@ -562,8 +562,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"Бөгөт коюу"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g> аккаунтуна которулуу"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Жаңы колдонуучу түзүлүүдө…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"Ылакап аты"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Конок кошуу"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"Конокту өчүрүү"</string>
diff --git a/packages/SettingsLib/res/values-lo/strings.xml b/packages/SettingsLib/res/values-lo/strings.xml
index 565fd30..a9d1f29 100644
--- a/packages/SettingsLib/res/values-lo/strings.xml
+++ b/packages/SettingsLib/res/values-lo/strings.xml
@@ -562,8 +562,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"ຕັ້ງການລັອກ"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"ສະຫຼັບໄປ <xliff:g id="USER_NAME">%s</xliff:g>"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"ກຳລັງສ້າງຜູ້ໃຊ້ໃໝ່…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"ຊື່ຫຼິ້ນ"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"ເພີ່ມແຂກ"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"ລຶບແຂກອອກ"</string>
diff --git a/packages/SettingsLib/res/values-lt/strings.xml b/packages/SettingsLib/res/values-lt/strings.xml
index e35a3088..2fe28d2 100644
--- a/packages/SettingsLib/res/values-lt/strings.xml
+++ b/packages/SettingsLib/res/values-lt/strings.xml
@@ -564,8 +564,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"Nustatyti užraktą"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"Perjungti į <xliff:g id="USER_NAME">%s</xliff:g>"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Kuriamas naujas naudotojas…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"Slapyvardis"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Pridėti svečią"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"Pašalinti svečią"</string>
diff --git a/packages/SettingsLib/res/values-lv/strings.xml b/packages/SettingsLib/res/values-lv/strings.xml
index eb8c808..3b7b73b 100644
--- a/packages/SettingsLib/res/values-lv/strings.xml
+++ b/packages/SettingsLib/res/values-lv/strings.xml
@@ -563,8 +563,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"Iestatīt bloķēšanu"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"Pārslēgties uz: <xliff:g id="USER_NAME">%s</xliff:g>"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Notiek jauna lietotāja izveide…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"Segvārds"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Pievienot viesi"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"Noņemt viesi"</string>
diff --git a/packages/SettingsLib/res/values-mk/strings.xml b/packages/SettingsLib/res/values-mk/strings.xml
index d0e5996..d027474 100644
--- a/packages/SettingsLib/res/values-mk/strings.xml
+++ b/packages/SettingsLib/res/values-mk/strings.xml
@@ -562,8 +562,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"Постави заклучување"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"Префрли на <xliff:g id="USER_NAME">%s</xliff:g>"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Се создава нов корисник…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"Прекар"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Додај гостин"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"Отстрани гостин"</string>
diff --git a/packages/SettingsLib/res/values-ml/strings.xml b/packages/SettingsLib/res/values-ml/strings.xml
index f5bf135..c553bdc 100644
--- a/packages/SettingsLib/res/values-ml/strings.xml
+++ b/packages/SettingsLib/res/values-ml/strings.xml
@@ -562,8 +562,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"ലോക്ക് സജ്ജീകരിക്കുക"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g> എന്നതിലേക്ക് മാറുക"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"പുതിയ ഉപയോക്താവിനെ സൃഷ്‌ടിക്കുന്നു…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"വിളിപ്പേര്"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"അതിഥിയെ ചേർക്കുക"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"അതിഥിയെ നീക്കം ചെയ്യുക"</string>
diff --git a/packages/SettingsLib/res/values-mn/strings.xml b/packages/SettingsLib/res/values-mn/strings.xml
index 3d3f710..48278dc 100644
--- a/packages/SettingsLib/res/values-mn/strings.xml
+++ b/packages/SettingsLib/res/values-mn/strings.xml
@@ -562,8 +562,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"Түгжээг тохируулах"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g> руу сэлгэх"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Шинэ хэрэглэгч үүсгэж байна…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"Хоч"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Зочин нэмэх"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"Зочин хасах"</string>
diff --git a/packages/SettingsLib/res/values-mr/strings.xml b/packages/SettingsLib/res/values-mr/strings.xml
index f57b164..a0eecde 100644
--- a/packages/SettingsLib/res/values-mr/strings.xml
+++ b/packages/SettingsLib/res/values-mr/strings.xml
@@ -562,8 +562,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"लॉक सेट करा"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g> वर स्विच करा"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"नवीन वापरकर्ता तयार करत आहे…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"टोपणनाव"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"अतिथी जोडा"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"अतिथी काढून टाका"</string>
diff --git a/packages/SettingsLib/res/values-ms/strings.xml b/packages/SettingsLib/res/values-ms/strings.xml
index 28d9e8e..5c2fcb7 100644
--- a/packages/SettingsLib/res/values-ms/strings.xml
+++ b/packages/SettingsLib/res/values-ms/strings.xml
@@ -562,8 +562,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"Tetapkan kunci"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"Tukar kepada <xliff:g id="USER_NAME">%s</xliff:g>"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Mencipta pengguna baharu…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"Nama panggilan"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Tambah tetamu"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"Alih keluar tetamu"</string>
diff --git a/packages/SettingsLib/res/values-my/strings.xml b/packages/SettingsLib/res/values-my/strings.xml
index e5df8cbf..7f3fc67 100644
--- a/packages/SettingsLib/res/values-my/strings.xml
+++ b/packages/SettingsLib/res/values-my/strings.xml
@@ -507,7 +507,7 @@
     <string name="alarms_and_reminders_label" msgid="6918395649731424294">"နှိုးစက်နှင့် သတိပေးချက်များ"</string>
     <string name="alarms_and_reminders_switch_title" msgid="186992351401152744">"နှိုးစက် (သို့) သတိပေးချက်များ သတ်မှတ်ခွင့်ပြုရန်"</string>
     <string name="alarms_and_reminders_title" msgid="2988400785896875237">"နှိုးစက်နှင့် သတိပေးချက်များ"</string>
-    <string name="alarms_and_reminders_footer_title" msgid="5882788882647778753">"နှိုးစက်သတ်မှတ်ရန် (သို့) အချိန်သတ်မှတ်ချက်ပါသည့် အစီအစဉ်များဆွဲရန် ဤအက်ပ်ကို ခွင့်ပြုပါ။ သင်က စက်ကိုအသုံးမပြုနေသည့် အချိန်တွင်လည်း စတင်ရန်နှင့် အလုပ်လုပ်နေရန် အက်ပ်ကို ခွင့်ပြုပါမည်။ ဤခွင့်ပြုချက်ကို ရုတ်သိမ်းခြင်းက အက်ပ်ကို ချွတ်ယွင်းစေမည်ဖြစ်ကြောင်း သတိပြုပါ၊ အထူးသဖြင့် အက်ပ်က သတ်မှတ်ထားသော မည်သည့်နှိုးစက်မျှ အလုပ်မလုပ်တော့ပါ။"</string>
+    <string name="alarms_and_reminders_footer_title" msgid="5882788882647778753">"နှိုးစက်သတ်မှတ်ရန် (သို့) အချိန်သတ်မှတ်ချက်ပါသည့် အစီအစဉ်များဆွဲရန် ဤအက်ပ်ကို ခွင့်ပြုပါ။ ၎င်းက စက်ကို သင်အသုံးမပြုသော်လည်း အက်ပ်ကို နှိုးရန်နှင့် အလုပ်လုပ်နေရန် ခွင့်ပြုမည်။ ဤခွင့်ပြုချက်ကို ရုတ်သိမ်းခြင်းက အက်ပ်ကို ချွတ်ယွင်းစေမည်ဖြစ်ကြောင်း သတိပြုပါ၊ အထူးသဖြင့် အက်ပ်က သတ်မှတ်ထားသော မည်သည့်နှိုးစက်မျှ အလုပ်မလုပ်တော့ပါ။"</string>
     <string name="keywords_alarms_and_reminders" msgid="8882739572152019456">"အချိန်ဇယား၊ နှိုးစက်၊ သတိပေးချက်၊ အစီအစဉ်"</string>
     <string name="zen_mode_enable_dialog_turn_on" msgid="6418297231575050426">"ဖွင့်ရန်"</string>
     <string name="zen_mode_settings_turn_on_dialog_title" msgid="2760567063190790696">"\'မနှောင့်ယှက်ရ\' ဖွင့်ခြင်း"</string>
@@ -562,8 +562,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"သော့ချရန် သတ်မှတ်ပါ"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g> သို့ ပြောင်းရန်"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"အသုံးပြုသူအသစ် ပြုလုပ်နေသည်…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"နာမည်ပြောင်"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"ဧည့်သည့် ထည့်ရန်"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"ဧည့်သည်ကို ဖယ်ထုတ်ရန်"</string>
diff --git a/packages/SettingsLib/res/values-nb/strings.xml b/packages/SettingsLib/res/values-nb/strings.xml
index a0c5651..24c982f 100644
--- a/packages/SettingsLib/res/values-nb/strings.xml
+++ b/packages/SettingsLib/res/values-nb/strings.xml
@@ -507,7 +507,7 @@
     <string name="alarms_and_reminders_label" msgid="6918395649731424294">"Alarmer og påminnelser"</string>
     <string name="alarms_and_reminders_switch_title" msgid="186992351401152744">"Gi tillatelse til å angi alarmer og påminnelser"</string>
     <string name="alarms_and_reminders_title" msgid="2988400785896875237">"Alarmer og påminnelser"</string>
-    <string name="alarms_and_reminders_footer_title" msgid="5882788882647778753">"Tillat denne appen å planlegge alarmer eller andre tidsbaserte hendelser. Dette gjør at appen kan starte og kjøre, selv når du ikke bruker enheten. Hvis du opphever denne tillatelsen, kan det føre til feil med appen, blant annet at alarmer som appen har planlagt, ikke fungerer lenger."</string>
+    <string name="alarms_and_reminders_footer_title" msgid="5882788882647778753">"Dette gjør at appen kan planlegge alarmer eller andre tidsbaserte hendelser. Dette gjør at appen kan starte og kjøre, selv når du ikke bruker enheten. Hvis du opphever denne tillatelsen, kan det føre til feil med appen, spesifikt at alarmer som appen har planlagt, ikke fungerer lenger."</string>
     <string name="keywords_alarms_and_reminders" msgid="8882739572152019456">"tidsplan, alarm, påminnelse, hendelse"</string>
     <string name="zen_mode_enable_dialog_turn_on" msgid="6418297231575050426">"Slå på"</string>
     <string name="zen_mode_settings_turn_on_dialog_title" msgid="2760567063190790696">"Slå på Ikke forstyrr"</string>
@@ -562,8 +562,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"Angi lås"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"Bytt til <xliff:g id="USER_NAME">%s</xliff:g>"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Oppretter en ny bruker …"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"Kallenavn"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Legg til en gjest"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"Fjern gjesten"</string>
diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml
index c98b4ae..ca9dd29 100644
--- a/packages/SettingsLib/res/values-ne/strings.xml
+++ b/packages/SettingsLib/res/values-ne/strings.xml
@@ -562,8 +562,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"लक सेट गर्नुहोस्"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"प्रयोगकर्ता बदलेर <xliff:g id="USER_NAME">%s</xliff:g> पार्नुहोस्"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"नयाँ प्रयोगकर्ता बनाउँदै…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"उपनाम"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"अतिथि थप्नुहोस्"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"अतिथि हटाउनुहोस्"</string>
diff --git a/packages/SettingsLib/res/values-nl/strings.xml b/packages/SettingsLib/res/values-nl/strings.xml
index 8139fe3..2126b1f 100644
--- a/packages/SettingsLib/res/values-nl/strings.xml
+++ b/packages/SettingsLib/res/values-nl/strings.xml
@@ -562,8 +562,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"Vergrendeling instellen"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"Overschakelen naar <xliff:g id="USER_NAME">%s</xliff:g>"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Nieuwe gebruiker maken…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"Bijnaam"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Gast toevoegen"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"Gast verwijderen"</string>
diff --git a/packages/SettingsLib/res/values-or/strings.xml b/packages/SettingsLib/res/values-or/strings.xml
index db404b8..b6da15cc 100644
--- a/packages/SettingsLib/res/values-or/strings.xml
+++ b/packages/SettingsLib/res/values-or/strings.xml
@@ -562,8 +562,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"ଲକ୍‌ ସେଟ୍‌ କରନ୍ତୁ"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g>କୁ ସ୍ୱିଚ୍ କରନ୍ତୁ"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"ନୂଆ ଉପଯୋଗକର୍ତ୍ତା ତିଆରି କରାଯାଉଛି…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"ଡାକନାମ"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"ଅତିଥି ଯୋଗ କରନ୍ତୁ"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"ଅତିଥିଙ୍କୁ କାଢ଼ି ଦିଅନ୍ତୁ"</string>
diff --git a/packages/SettingsLib/res/values-pa/strings.xml b/packages/SettingsLib/res/values-pa/strings.xml
index c8af67b..f958c10 100644
--- a/packages/SettingsLib/res/values-pa/strings.xml
+++ b/packages/SettingsLib/res/values-pa/strings.xml
@@ -562,8 +562,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">" ਲਾਕ  ਸੈੱਟ ਕਰੋ"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g> \'ਤੇ ਜਾਓ"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"ਨਵਾਂ ਵਰਤੋਂਕਾਰ ਬਣਾਇਆ ਜਾ ਰਿਹਾ ਹੈ…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"ਉਪਨਾਮ"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"ਮਹਿਮਾਨ ਸ਼ਾਮਲ ਕਰੋ"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"ਮਹਿਮਾਨ ਹਟਾਓ"</string>
diff --git a/packages/SettingsLib/res/values-pl/strings.xml b/packages/SettingsLib/res/values-pl/strings.xml
index 687f663..9bf3873 100644
--- a/packages/SettingsLib/res/values-pl/strings.xml
+++ b/packages/SettingsLib/res/values-pl/strings.xml
@@ -564,8 +564,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"Ustaw blokadę"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"Przełącz na: <xliff:g id="USER_NAME">%s</xliff:g>"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Tworzę nowego użytkownika…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"Pseudonim"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Dodaj gościa"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"Usuń gościa"</string>
diff --git a/packages/SettingsLib/res/values-pt-rBR/strings.xml b/packages/SettingsLib/res/values-pt-rBR/strings.xml
index 34fff26..395347e 100644
--- a/packages/SettingsLib/res/values-pt-rBR/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rBR/strings.xml
@@ -562,8 +562,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"Definir bloqueio"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"Mudar para <xliff:g id="USER_NAME">%s</xliff:g>"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Criando novo usuário…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"Apelido"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Adicionar convidado"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"Remover convidado"</string>
diff --git a/packages/SettingsLib/res/values-pt-rPT/strings.xml b/packages/SettingsLib/res/values-pt-rPT/strings.xml
index bb1d8b2..1c502d2 100644
--- a/packages/SettingsLib/res/values-pt-rPT/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rPT/strings.xml
@@ -562,8 +562,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"Definir bloqueio"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"Mudar para <xliff:g id="USER_NAME">%s</xliff:g>"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"A criar novo utilizador…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"Pseudónimo"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Adicionar convidado"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"Remover convidado"</string>
diff --git a/packages/SettingsLib/res/values-pt/strings.xml b/packages/SettingsLib/res/values-pt/strings.xml
index 34fff26..395347e 100644
--- a/packages/SettingsLib/res/values-pt/strings.xml
+++ b/packages/SettingsLib/res/values-pt/strings.xml
@@ -562,8 +562,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"Definir bloqueio"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"Mudar para <xliff:g id="USER_NAME">%s</xliff:g>"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Criando novo usuário…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"Apelido"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Adicionar convidado"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"Remover convidado"</string>
diff --git a/packages/SettingsLib/res/values-ro/strings.xml b/packages/SettingsLib/res/values-ro/strings.xml
index 5e5c044..5527c66 100644
--- a/packages/SettingsLib/res/values-ro/strings.xml
+++ b/packages/SettingsLib/res/values-ro/strings.xml
@@ -563,8 +563,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"Configurați blocarea"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"Treceți la <xliff:g id="USER_NAME">%s</xliff:g>"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Se creează un utilizator nou…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"Pseudonim"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Adăugați un invitat"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"Ștergeți invitatul"</string>
diff --git a/packages/SettingsLib/res/values-ru/strings.xml b/packages/SettingsLib/res/values-ru/strings.xml
index b9b2edb..cd80bc5 100644
--- a/packages/SettingsLib/res/values-ru/strings.xml
+++ b/packages/SettingsLib/res/values-ru/strings.xml
@@ -564,8 +564,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"Включить блокировку"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"Переключиться на этот аккаунт: <xliff:g id="USER_NAME">%s</xliff:g>"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Создаем нового пользователя…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"Псевдоним"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Добавить гостя"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"Удалить аккаунт гостя"</string>
diff --git a/packages/SettingsLib/res/values-si/strings.xml b/packages/SettingsLib/res/values-si/strings.xml
index cd25803..f75a3da 100644
--- a/packages/SettingsLib/res/values-si/strings.xml
+++ b/packages/SettingsLib/res/values-si/strings.xml
@@ -562,8 +562,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"අගුල සකසන්න"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g> වෙත මාරු වන්න"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"නව පරිශීලක තනමින්…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"අපනාමය"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"අමුත්තා එක් කරන්න"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"අමුත්තා ඉවත් කරන්න"</string>
diff --git a/packages/SettingsLib/res/values-sk/strings.xml b/packages/SettingsLib/res/values-sk/strings.xml
index 03c3cb2..14c3b08 100644
--- a/packages/SettingsLib/res/values-sk/strings.xml
+++ b/packages/SettingsLib/res/values-sk/strings.xml
@@ -564,8 +564,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"Nastaviť uzamknutie"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"Prepnúť na používateľa <xliff:g id="USER_NAME">%s</xliff:g>"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Vytvára sa nový používateľ…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"Prezývka"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Pridať hosťa"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"Odobrať hosťa"</string>
diff --git a/packages/SettingsLib/res/values-sl/strings.xml b/packages/SettingsLib/res/values-sl/strings.xml
index b2635fb..2c24e68 100644
--- a/packages/SettingsLib/res/values-sl/strings.xml
+++ b/packages/SettingsLib/res/values-sl/strings.xml
@@ -564,8 +564,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"Nastavi zaklepanje"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"Preklop na račun <xliff:g id="USER_NAME">%s</xliff:g>"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Ustvarjanje novega uporabnika …"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"Vzdevek"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Dodajanje gosta"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"Odstranitev gosta"</string>
diff --git a/packages/SettingsLib/res/values-sq/strings.xml b/packages/SettingsLib/res/values-sq/strings.xml
index e57ede6..7496835 100644
--- a/packages/SettingsLib/res/values-sq/strings.xml
+++ b/packages/SettingsLib/res/values-sq/strings.xml
@@ -562,8 +562,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"Cakto kyçjen"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"Kalo te <xliff:g id="USER_NAME">%s</xliff:g>"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Po krijohet një përdorues i ri…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"Pseudonimi"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Shto të ftuar"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"Hiq të ftuarin"</string>
diff --git a/packages/SettingsLib/res/values-sr/strings.xml b/packages/SettingsLib/res/values-sr/strings.xml
index c0a9022..8db0e06 100644
--- a/packages/SettingsLib/res/values-sr/strings.xml
+++ b/packages/SettingsLib/res/values-sr/strings.xml
@@ -563,8 +563,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"Подеси закључавање"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"Пређи на корисника <xliff:g id="USER_NAME">%s</xliff:g>"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Прави се нови корисник…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"Надимак"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Додај госта"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"Уклони госта"</string>
diff --git a/packages/SettingsLib/res/values-sv/strings.xml b/packages/SettingsLib/res/values-sv/strings.xml
index da22032..b0e1de9 100644
--- a/packages/SettingsLib/res/values-sv/strings.xml
+++ b/packages/SettingsLib/res/values-sv/strings.xml
@@ -562,8 +562,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"Konfigurera lås"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"Byt till <xliff:g id="USER_NAME">%s</xliff:g>"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Skapar ny användare …"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"Smeknamn"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Lägg till gäst"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"Ta bort gäst"</string>
diff --git a/packages/SettingsLib/res/values-sw/strings.xml b/packages/SettingsLib/res/values-sw/strings.xml
index 4da8184..537f226 100644
--- a/packages/SettingsLib/res/values-sw/strings.xml
+++ b/packages/SettingsLib/res/values-sw/strings.xml
@@ -562,8 +562,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"Weka ufunguo"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"Badili utumie <xliff:g id="USER_NAME">%s</xliff:g>"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Inaweka mtumiaji mpya…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"Jina wakilishi"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Ongeza mgeni"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"Ondoa mgeni"</string>
diff --git a/packages/SettingsLib/res/values-ta/strings.xml b/packages/SettingsLib/res/values-ta/strings.xml
index 535bbf2..7aec7bc 100644
--- a/packages/SettingsLib/res/values-ta/strings.xml
+++ b/packages/SettingsLib/res/values-ta/strings.xml
@@ -562,8 +562,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"பூட்டை அமை"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g>க்கு மாறு"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"புதிய பயனரை உருவாக்குகிறது…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"புனைப்பெயர்"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"கெஸ்ட்டைச் சேர்"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"கெஸ்ட்டை அகற்று"</string>
diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml
index 31f7519..3ae2d90 100644
--- a/packages/SettingsLib/res/values-te/strings.xml
+++ b/packages/SettingsLib/res/values-te/strings.xml
@@ -562,8 +562,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"లాక్‌ను సెట్ చేయి"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g>కు మార్చు"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"కొత్త యూజర్‌ను క్రియేట్ చేస్తోంది…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"మారుపేరు"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"గెస్ట్‌ను జోడించండి"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"అతిథిని తీసివేయండి"</string>
diff --git a/packages/SettingsLib/res/values-th/strings.xml b/packages/SettingsLib/res/values-th/strings.xml
index 59ace4a..b566d88 100644
--- a/packages/SettingsLib/res/values-th/strings.xml
+++ b/packages/SettingsLib/res/values-th/strings.xml
@@ -562,8 +562,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"ตั้งค่าล็อก"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"เปลี่ยนเป็น <xliff:g id="USER_NAME">%s</xliff:g>"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"กำลังสร้างผู้ใช้ใหม่…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"ชื่อเล่น"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"เพิ่มผู้เข้าร่วม"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"นำผู้เข้าร่วมออก"</string>
diff --git a/packages/SettingsLib/res/values-tl/strings.xml b/packages/SettingsLib/res/values-tl/strings.xml
index 0c072ca..2a8109c 100644
--- a/packages/SettingsLib/res/values-tl/strings.xml
+++ b/packages/SettingsLib/res/values-tl/strings.xml
@@ -562,8 +562,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"Itakda ang lock"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"Lumipat sa <xliff:g id="USER_NAME">%s</xliff:g>"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Gumagawa ng bagong user…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"Nickname"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Magdagdag ng bisita"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"Alisin ang bisita"</string>
diff --git a/packages/SettingsLib/res/values-tr/strings.xml b/packages/SettingsLib/res/values-tr/strings.xml
index fc9bd9e..03d975e 100644
--- a/packages/SettingsLib/res/values-tr/strings.xml
+++ b/packages/SettingsLib/res/values-tr/strings.xml
@@ -562,8 +562,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"Kilidi ayarla"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g> hesabına geç"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Yeni kullanıcı oluşturuluyor…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"Takma ad"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Misafir ekle"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"Misafir oturumunu kaldır"</string>
diff --git a/packages/SettingsLib/res/values-uk/strings.xml b/packages/SettingsLib/res/values-uk/strings.xml
index e08b515..5b1130b21 100644
--- a/packages/SettingsLib/res/values-uk/strings.xml
+++ b/packages/SettingsLib/res/values-uk/strings.xml
@@ -564,8 +564,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"Налаштувати блокування"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"Перейти до користувача <xliff:g id="USER_NAME">%s</xliff:g>"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Створення нового користувача…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"Псевдонім"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Додати гостя"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"Видалити гостя"</string>
diff --git a/packages/SettingsLib/res/values-ur/strings.xml b/packages/SettingsLib/res/values-ur/strings.xml
index 0909efb..2ce5204 100644
--- a/packages/SettingsLib/res/values-ur/strings.xml
+++ b/packages/SettingsLib/res/values-ur/strings.xml
@@ -567,8 +567,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"لاک سیٹ کریں"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"‫<xliff:g id="USER_NAME">%s</xliff:g> پر سوئچ کریں"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"نیا صارف تخلیق کرنا…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"عرفی نام"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"مہمان کو شامل کریں"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"مہمان کو ہٹائیں"</string>
diff --git a/packages/SettingsLib/res/values-uz/strings.xml b/packages/SettingsLib/res/values-uz/strings.xml
index f83fe1d..4eb0e48 100644
--- a/packages/SettingsLib/res/values-uz/strings.xml
+++ b/packages/SettingsLib/res/values-uz/strings.xml
@@ -562,8 +562,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"Qulf o‘rnatish"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"Bunga almashish: <xliff:g id="USER_NAME">%s</xliff:g>"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Yangi foydalanuvchi yaratilmoqda…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"Nik"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Mehmon kiritish"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"Mehmonni olib tashlash"</string>
diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml
index ab415f9..f769249 100644
--- a/packages/SettingsLib/res/values-vi/strings.xml
+++ b/packages/SettingsLib/res/values-vi/strings.xml
@@ -562,8 +562,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"Thiết lập khóa"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"Chuyển sang <xliff:g id="USER_NAME">%s</xliff:g>"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Đang tạo người dùng mới…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"Biệt hiệu"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Thêm khách"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"Xóa phiên khách"</string>
diff --git a/packages/SettingsLib/res/values-zh-rCN/strings.xml b/packages/SettingsLib/res/values-zh-rCN/strings.xml
index 2d95ab1a..cc5b97b 100644
--- a/packages/SettingsLib/res/values-zh-rCN/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rCN/strings.xml
@@ -562,8 +562,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"设置屏幕锁定方式"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"切换到<xliff:g id="USER_NAME">%s</xliff:g>"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"正在创建新用户…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"昵称"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"添加访客"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"移除访客"</string>
diff --git a/packages/SettingsLib/res/values-zh-rHK/strings.xml b/packages/SettingsLib/res/values-zh-rHK/strings.xml
index 3314f82..d49707b 100644
--- a/packages/SettingsLib/res/values-zh-rHK/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rHK/strings.xml
@@ -562,8 +562,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"設定上鎖畫面"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"切換至<xliff:g id="USER_NAME">%s</xliff:g>"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"正在建立新使用者…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"暱稱"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"新增訪客"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"移除訪客"</string>
diff --git a/packages/SettingsLib/res/values-zh-rTW/strings.xml b/packages/SettingsLib/res/values-zh-rTW/strings.xml
index 95eae87..7649e44 100644
--- a/packages/SettingsLib/res/values-zh-rTW/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rTW/strings.xml
@@ -562,8 +562,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"設定鎖定"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"切換至<xliff:g id="USER_NAME">%s</xliff:g>"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"正在建立新使用者…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"暱稱"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"新增訪客"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"移除訪客"</string>
diff --git a/packages/SettingsLib/res/values-zu/strings.xml b/packages/SettingsLib/res/values-zu/strings.xml
index f5322a1..d0ddec5 100644
--- a/packages/SettingsLib/res/values-zu/strings.xml
+++ b/packages/SettingsLib/res/values-zu/strings.xml
@@ -562,8 +562,6 @@
     <string name="user_set_lock_button" msgid="1427128184982594856">"Setha ukukhiya"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"Shintshela ku-<xliff:g id="USER_NAME">%s</xliff:g>"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Idala umsebenzisi omusha…"</string>
-    <!-- no translation found for add_user_failed (4809887794313944872) -->
-    <skip />
     <string name="user_nickname" msgid="262624187455825083">"Isiteketiso"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Engeza isivakashi"</string>
     <string name="guest_exit_guest" msgid="5908239569510734136">"Susa isihambeli"</string>
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index fbb84fd..7631671 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -214,6 +214,10 @@
     <!-- Default for Settings.System.VIBRATE_WHEN_RINGING -->
     <bool name="def_vibrate_when_ringing">false</bool>
 
+    <!-- Default for Settings.Global.CELL_ON; see PhoneConstants.CELL_ON_FLAG.
+        0: cellular off; 1: cellular on. -->
+    <integer name="def_cell_on">1</integer>
+
     <!-- Default for Settings.Global.APPLY_RAMPING_RINGER -->
     <bool name="def_apply_ramping_ringer">false</bool>
 
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
index 268603f..cdf274f 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
@@ -2467,6 +2467,9 @@
             loadBooleanSetting(stmt, Settings.Global.BLUETOOTH_ON,
                     R.bool.def_bluetooth_on);
 
+            loadIntegerSetting(stmt, Settings.Global.CELL_ON,
+                    R.integer.def_cell_on);
+
             // Enable or disable Cell Broadcast SMS
             loadSetting(stmt, Settings.Global.CDMA_CELL_BROADCAST_SMS,
                     RILConstants.CDMA_CELL_BROADCAST_SMS_DISABLED);
diff --git a/packages/Shell/res/values-ne/strings.xml b/packages/Shell/res/values-ne/strings.xml
index 3c58796..69da552 100644
--- a/packages/Shell/res/values-ne/strings.xml
+++ b/packages/Shell/res/values-ne/strings.xml
@@ -42,6 +42,6 @@
     <string name="bugreport_info_name" msgid="4414036021935139527">"फाइलको नाम"</string>
     <string name="bugreport_info_title" msgid="2306030793918239804">"बगको शीर्षक"</string>
     <string name="bugreport_info_description" msgid="5072835127481627722">"बगको सारांश"</string>
-    <string name="save" msgid="4781509040564835759">"सुरक्षित गर्नुहोस्"</string>
+    <string name="save" msgid="4781509040564835759">"सेभ गर्नुहोस्"</string>
     <string name="bugreport_intent_chooser_title" msgid="7605709494790894076">"बग रिपोर्ट सेयर गर्नुहोस्"</string>
 </resources>
diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java b/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java
index 504e18a..56b940c 100644
--- a/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java
+++ b/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java
@@ -256,6 +256,14 @@
         }
 
         setupAlert();
+
+        ListView listView = mAlert.getListView();
+        if (listView != null) {
+            // List view needs to gain focus in order for RSB to work.
+            if (!listView.requestFocus()) {
+                Log.e(TAG, "Unable to gain focus! RSB may not work properly.");
+            }
+        }
     }
     @Override
     public void onSaveInstanceState(Bundle outState) {
diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING
index 0d18b8d..24bffa1 100644
--- a/packages/SystemUI/TEST_MAPPING
+++ b/packages/SystemUI/TEST_MAPPING
@@ -53,8 +53,9 @@
 
   // Curious where your @Scenario tests will run?
   //
-  // @Ignore or @FlakyTest: nowhere
-  // @Staging: in staged-postsubmit, but not postsubmit or presubmit
+  // @Ignore: nowhere
+  // @Staging or @FlakyTest: in staged-postsubmit, but not postsubmit or
+  // 	presubmit
   // @Postsubmit: in postsubmit and staged-postsubmit, but not presubmit
   // none of the above: in presubmit, postsubmit, and staged-postsubmit
   //
@@ -98,9 +99,6 @@
         },
         {
             "exclude-annotation": "org.junit.Ignore"
-        },
-        {
-            "exclude-annotation": "androidx.test.filters.FlakyTest"
         }
       ]
     }
diff --git a/packages/SystemUI/res-keyguard/values-eu/strings.xml b/packages/SystemUI/res-keyguard/values-eu/strings.xml
index 93fe516..9f00e668 100644
--- a/packages/SystemUI/res-keyguard/values-eu/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-eu/strings.xml
@@ -106,7 +106,7 @@
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN kodea idatzi beharko duzu gailua berrabiarazten denean"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Pasahitza idatzi beharko duzu gailua berrabiarazten denean"</string>
     <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Eredua behar da gailua babestuago izateko"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"PIN kodea behar da gailua babestuago izateko"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"PINa behar da gailua babestuago izateko"</string>
     <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Pasahitza behar da gailua babestuago izateko"</string>
     <string name="kg_prompt_reason_switch_profiles_pattern" msgid="1922016914701991230">"Eredua marraztu beharko duzu profilez aldatzen baduzu"</string>
     <string name="kg_prompt_reason_switch_profiles_pin" msgid="6490434826361055400">"PIN kodea idatzi beharko duzu profilez aldatzen baduzu"</string>
diff --git a/packages/SystemUI/res-keyguard/values-ne/strings.xml b/packages/SystemUI/res-keyguard/values-ne/strings.xml
index 6f8a108..8ff4f14 100644
--- a/packages/SystemUI/res-keyguard/values-ne/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ne/strings.xml
@@ -51,7 +51,7 @@
     <string name="keyguard_sim_puk_locked_message" msgid="6253830777745450550">"SIM कार्ड PUK-लक भएको छ।"</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="2394023844117630429">"SIM कार्ड अनलक गरिँदै..."</string>
     <string name="keyguard_accessibility_pin_area" msgid="7403009340414014734">"PIN क्षेत्र"</string>
-    <string name="keyguard_accessibility_password" msgid="3524161948484801450">"यन्त्रको पासवर्ड"</string>
+    <string name="keyguard_accessibility_password" msgid="3524161948484801450">"डिभाइसको पासवर्ड"</string>
     <string name="keyguard_accessibility_sim_pin_area" msgid="6272116591533888062">"SIM को PIN क्षेत्र"</string>
     <string name="keyguard_accessibility_sim_puk_area" msgid="5537294043180237374">"SIM को PUK क्षेत्र"</string>
     <string name="keyguard_accessibility_next_alarm" msgid="4492876946798984630">"अर्को अलार्म <xliff:g id="ALARM">%1$s</xliff:g> का लागि सेट गरियो"</string>
@@ -71,7 +71,7 @@
     <string name="kg_pattern_instructions" msgid="5376036737065051736">"आफ्नो ढाँचा कोर्नुहोस्"</string>
     <string name="kg_sim_pin_instructions" msgid="1942424305184242951">"SIM को PIN प्रविष्टि गर्नुहोस्।"</string>
     <string name="kg_sim_pin_instructions_multi" msgid="3639863309953109649">"\"<xliff:g id="CARRIER">%1$s</xliff:g>\" को SIM को PIN प्रविष्टि गर्नुहोस्।"</string>
-    <string name="kg_sim_lock_esim_instructions" msgid="5577169988158738030">"<xliff:g id="PREVIOUS_MSG">%1$s</xliff:g> मोबाइल सेवा बिना यन्त्रको प्रयोग गर्न eSIM लाई असक्षम पार्नुहोस्।"</string>
+    <string name="kg_sim_lock_esim_instructions" msgid="5577169988158738030">"<xliff:g id="PREVIOUS_MSG">%1$s</xliff:g> मोबाइल सेवा बिना डिभाइसको प्रयोग गर्न eSIM लाई असक्षम पार्नुहोस्।"</string>
     <string name="kg_pin_instructions" msgid="822353548385014361">"PIN प्रविष्टि गर्नुहोस्"</string>
     <string name="kg_password_instructions" msgid="324455062831719903">"पासवर्ड प्रविष्टि गर्नुहोस्"</string>
     <string name="kg_puk_enter_puk_hint" msgid="3005288372875367017">"SIM कार्ड अहिले असक्षम छ। सुचारु गर्नको लागि PUK कोड प्रविष्टि गर्नुहोस्।  विवरणको लागि सेवा प्रदायकलाई सम्पर्क गर्नुहोस्।"</string>
@@ -85,7 +85,7 @@
     <string name="kg_login_too_many_attempts" msgid="4519957179182578690">"अत्यन्त धेरै ढाँचा कोर्ने प्रयासहरू"</string>
     <string name="kg_too_many_failed_pin_attempts_dialog_message" msgid="544687656831558971">"तपाईंले <xliff:g id="NUMBER_0">%1$d</xliff:g> पटक गलत तरिकाले आफ्नो PIN प्रविष्ट गर्नुभएको छ। \n\n<xliff:g id="NUMBER_1">%2$d</xliff:g> सेकेन्डमा फेरि प्रयास गर्नुहोस्।"</string>
     <string name="kg_too_many_failed_password_attempts_dialog_message" msgid="190984061975729494">"तपाईंले <xliff:g id="NUMBER_0">%1$d</xliff:g> पटक आफ्नो गलत पासवर्ड  प्रविष्ट गर्नुभएको छ। \n\n <xliff:g id="NUMBER_1">%2$d</xliff:g> सेकेन्डमा फेरि प्रयास गर्नुहोस्।"</string>
-    <string name="kg_too_many_failed_pattern_attempts_dialog_message" msgid="4252405904570284368">"तपाईंले <xliff:g id="NUMBER_0">%1$d</xliff:g> पटक गलत तरिकाले आफ्नो अनलक ढाँचा कोर्नुभएको छ। \n\n <xliff:g id="NUMBER_1">%2$d</xliff:g> सेकेन्डमा फेरि कोसिस गर्नुहोस्।"</string>
+    <string name="kg_too_many_failed_pattern_attempts_dialog_message" msgid="4252405904570284368">"तपाईंले <xliff:g id="NUMBER_0">%1$d</xliff:g> पटक गलत तरिकाले आफ्नो अनलक प्याटर्न कोर्नुभएको छ। \n\n <xliff:g id="NUMBER_1">%2$d</xliff:g> सेकेन्डमा फेरि कोसिस गर्नुहोस्।"</string>
     <string name="kg_password_wrong_pin_code_pukked" msgid="8047350661459040581">"SIM को PIN कोड गलत छ। तपाईंले अब आफ्नो यन्त्र खोल्न आफ्नो सेवा प्रदायकलाई सम्पर्क गर्नै पर्ने हुन्छ।"</string>
     <plurals name="kg_password_wrong_pin_code" formatted="false" msgid="7030584350995485026">
       <item quantity="other">SIM को PIN कोड गलत छ, तपाईं अझै <xliff:g id="NUMBER_1">%d</xliff:g> पटक प्रयास गर्न सक्नुहुन्छ।</item>
diff --git a/packages/SystemUI/res-product/values-ne/strings.xml b/packages/SystemUI/res-product/values-ne/strings.xml
index 148cb51..1d368f4 100644
--- a/packages/SystemUI/res-product/values-ne/strings.xml
+++ b/packages/SystemUI/res-product/values-ne/strings.xml
@@ -22,7 +22,7 @@
     <string name="dock_alignment_slow_charging" product="default" msgid="6997633396534416792">"अझ छिटो चार्ज गर्न फोनलाई फेरि मिलाउनुहोस्"</string>
     <string name="dock_alignment_not_charging" product="default" msgid="3980752926226749808">"तारविनै चार्ज गर्न फोनलाई फेरि मिलाउनुहोस्"</string>
     <string name="inattentive_sleep_warning_message" product="tv" msgid="6844464574089665063">"Android टिभी यन्त्र चाँडै निष्क्रिय हुने छ; सक्रिय राख्न कुनै बटन थिच्नुहोस्।"</string>
-    <string name="inattentive_sleep_warning_message" product="default" msgid="5693904520452332224">"यो यन्त्र चाँडै निष्क्रिय हुने छ; सक्रिय राख्न थिच्नुहोस्।"</string>
+    <string name="inattentive_sleep_warning_message" product="default" msgid="5693904520452332224">"यो डिभाइस चाँडै निष्क्रिय हुने छ; सक्रिय राख्न थिच्नुहोस्।"</string>
     <string name="keyguard_missing_sim_message" product="tablet" msgid="5018086454277963787">"ट्याब्लेटमा SIM कार्ड छैन।"</string>
     <string name="keyguard_missing_sim_message" product="default" msgid="7053347843877341391">"फोनमा SIM कार्ड छैन।"</string>
     <string name="kg_invalid_confirm_pin_hint" product="default" msgid="6278551068943958651">"PIN कोडहरू मिलेनन्"</string>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java
index 3db5440..d6356de 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java
@@ -242,12 +242,12 @@
         int childCount = 0;
         boolean hasBubbles = false;
         for (NotificationEntry entry : group.children.values()) {
-            if (mBubblesOptional.isPresent() && !mBubblesOptional.get()
+            if (mBubblesOptional.isPresent() && mBubblesOptional.get()
                     .isBubbleNotificationSuppressedFromShade(
                             entry.getKey(), entry.getSbn().getGroupKey())) {
-                childCount++;
-            } else {
                 hasBubbles = true;
+            } else {
+                childCount++;
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index c9011f4..fff688a 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -223,6 +223,11 @@
                 UserHandle.USER_ALL);
         mDeviceProvisionedController.addCallback(mDeviceProvisionedListener);
 
+        // All wallpaper color and keyguard logic only applies when Monet is enabled.
+        if (!mIsMonetEnabled) {
+            return;
+        }
+
         // Upon boot, make sure we have the most up to date colors
         mBgExecutor.execute(() -> {
             WallpaperColors systemColor = mWallpaperManager.getWallpaperColors(
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 74b79d5..81bb819 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -268,6 +268,7 @@
             public void onStop() {
                 mSysUiMainExecutor.execute(() -> {
                     if (oneHanded.isOneHandedEnabled()) {
+                        // Log metrics for 3-button navigation mode.
                         oneHanded.stopOneHanded(
                                 OneHandedUiEventLogger.EVENT_ONE_HANDED_TRIGGER_GESTURE_OUT);
                     } else if (oneHanded.isSwipeToNotificationEnabled()) {
diff --git a/packages/overlays/IconPackVictorThemePickerOverlay/Android.bp b/packages/overlays/IconPackVictorThemePickerOverlay/Android.bp
index a18ebb3..690d0a0 100644
--- a/packages/overlays/IconPackVictorThemePickerOverlay/Android.bp
+++ b/packages/overlays/IconPackVictorThemePickerOverlay/Android.bp
@@ -26,6 +26,5 @@
 runtime_resource_overlay {
     name: "IconPackVictorThemePickerOverlay",
     theme: "IconPackVictorThemePicker",
-    certificate: "platform",
     product_specific: true,
 }
diff --git a/rs/java/android/renderscript/ScriptIntrinsicBlend.java b/rs/java/android/renderscript/ScriptIntrinsicBlend.java
index a1c79ef..3109cd8 100644
--- a/rs/java/android/renderscript/ScriptIntrinsicBlend.java
+++ b/rs/java/android/renderscript/ScriptIntrinsicBlend.java
@@ -104,7 +104,7 @@
      * @param opt LaunchOptions for clipping
      */
     public void forEachSrc(Allocation ain, Allocation aout, Script.LaunchOptions opt) {
-        blend(1, ain, aout, null);
+        blend(1, ain, aout, opt);
     }
 
     /**
@@ -641,4 +641,3 @@
     }
 */
 }
-
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 55490ce..75d881b 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -133,12 +133,15 @@
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.nio.charset.StandardCharsets;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Date;
 import java.util.List;
 import java.util.Objects;
 import java.util.Set;
+import java.util.TimeZone;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 import java.util.function.Function;
@@ -173,6 +176,11 @@
     private static final String XML_ATTR_TIME_APPROVED = "time_approved";
     private static final String XML_FILE_NAME = "companion_device_manager_associations.xml";
 
+    private static DateFormat sDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+    static {
+        sDateFormat.setTimeZone(TimeZone.getDefault());
+    }
+
     private final CompanionDeviceManagerImpl mImpl;
     private final ConcurrentMap<Integer, AtomicFile> mUidToStorage = new ConcurrentHashMap<>();
     private PowerWhitelistManager mPowerWhitelistManager;
@@ -633,7 +641,7 @@
                             association.getDeviceMacAddress(),
                             association.getPackageName(),
                             association.getDeviceProfile(),
-                            active, /* notifyOnDeviceNearby */
+                            active /* notifyOnDeviceNearby */,
                             association.getTimeApprovedMs());
                 } else {
                     return association;
@@ -706,12 +714,40 @@
             synchronized (mLock) {
                 for (UserInfo user : getAllUsers()) {
                     forEach(mCachedAssociations.get(user.id), a -> {
-                        fout.append("  ")
-                                .append("u").append("" + a.getUserId()).append(": ")
-                                .append(a.getPackageName()).append(" - ")
-                                .append(a.getDeviceMacAddress()).append('\n');
+                        fout.append("  ").append(a.toString()).append('\n');
                     });
                 }
+
+            }
+            fout.append("Currently Connected Devices:").append('\n');
+            for (int i = 0, size = mCurrentlyConnectedDevices.size(); i < size; i++) {
+                fout.append("  ").append(mCurrentlyConnectedDevices.get(i)).append('\n');
+            }
+
+            fout.append("Devices Last Nearby:").append('\n');
+            for (int i = 0, size = mDevicesLastNearby.size(); i < size; i++) {
+                String device = mDevicesLastNearby.keyAt(i);
+                Date time = mDevicesLastNearby.valueAt(i);
+                fout.append("  ").append(device).append(" -> ")
+                        .append(sDateFormat.format(time)).append('\n');
+            }
+
+            fout.append("Discovery Service State:").append('\n');
+            for (int i = 0, size = mServiceConnectors.size(); i < size; i++) {
+                int userId = mServiceConnectors.keyAt(i);
+                fout.append("  ")
+                        .append("u").append(Integer.toString(userId)).append(": ")
+                        .append(Objects.toString(mServiceConnectors.valueAt(i)))
+                        .append('\n');
+            }
+
+            fout.append("Device Listener Services State:").append('\n');
+            for (int i = 0, size = mDeviceListenerServiceConnectors.size(); i < size; i++) {
+                int userId = mDeviceListenerServiceConnectors.keyAt(i);
+                fout.append("  ")
+                        .append("u").append(Integer.toString(userId)).append(": ")
+                        .append(Objects.toString(mDeviceListenerServiceConnectors.valueAt(i)))
+                        .append('\n');
             }
         }
     }
@@ -994,6 +1030,7 @@
         }
     }
 
+
     private Set<Association> getAllAssociations(
             int userId, @Nullable String packageFilter, @Nullable String addressFilter) {
         return CollectionUtils.filter(
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 5cbcacf..b3881d5 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -154,7 +154,7 @@
         "android.hardware.configstore-V1.0-java",
         "android.hardware.contexthub-V1.0-java",
         "android.hardware.rebootescrow-V1-java",
-        "android.hardware.soundtrigger-V2.3-java",
+        "android.hardware.soundtrigger-V2.4-java",
         "android.hardware.power.stats-V1-java",
         "android.hidl.manager-V1.2-java",
         "capture_state_listener-aidl-java",
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index a9eb2c1..4e39af7 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -30,6 +30,7 @@
 import android.content.pm.PackageManager.ComponentInfoFlags;
 import android.content.pm.PackageManager.PackageInfoFlags;
 import android.content.pm.PackageManager.ResolveInfoFlags;
+import android.content.pm.SigningDetails.CertCapabilities;
 import android.content.pm.overlay.OverlayPaths;
 import android.content.pm.parsing.component.ParsedMainComponent;
 import android.os.Bundle;
@@ -730,7 +731,7 @@
      * signing history for {@code serverUid} and with the {@code capability} specified.
      */
     public abstract boolean hasSignatureCapability(int serverUid, int clientUid,
-            @PackageParser.SigningDetails.CertCapabilities int capability);
+            @CertCapabilities int capability);
 
     /**
      * Get appIds of all available apps which specified android:sharedUserId in the manifest.
diff --git a/services/core/java/com/android/server/BinderCallsStatsService.java b/services/core/java/com/android/server/BinderCallsStatsService.java
index 9e126d7..78610a2 100644
--- a/services/core/java/com/android/server/BinderCallsStatsService.java
+++ b/services/core/java/com/android/server/BinderCallsStatsService.java
@@ -138,6 +138,14 @@
         private static final String SETTINGS_COLLECT_LATENCY_DATA_KEY = "collect_Latency_data";
         private static final String SETTINGS_LATENCY_OBSERVER_SAMPLING_INTERVAL_KEY =
                 "latency_observer_sampling_interval";
+        private static final String SETTINGS_LATENCY_OBSERVER_PUSH_INTERVAL_MINUTES_KEY =
+                "latency_observer_push_interval_minutes";
+        private static final String SETTINGS_LATENCY_HISTOGRAM_BUCKET_COUNT_KEY =
+                "latency_histogram_bucket_count";
+        private static final String SETTINGS_LATENCY_HISTOGRAM_FIRST_BUCKET_SIZE_KEY =
+                "latency_histogram_first_bucket_size";
+        private static final String SETTINGS_LATENCY_HISTOGRAM_BUCKET_SCALE_FACTOR_KEY =
+                "latency_histogram_bucket_scale_factor";
 
         private boolean mEnabled;
         private final Uri mUri = Settings.Global.getUriFor(Settings.Global.BINDER_CALLS_STATS);
@@ -198,10 +206,23 @@
                     mParser.getBoolean(SETTINGS_COLLECT_LATENCY_DATA_KEY,
                     BinderCallsStats.DEFAULT_COLLECT_LATENCY_DATA));
             // Binder latency observer settings.
-            mBinderCallsStats.getLatencyObserver().setSamplingInterval(mParser.getInt(
+            BinderLatencyObserver binderLatencyObserver = mBinderCallsStats.getLatencyObserver();
+            binderLatencyObserver.setSamplingInterval(mParser.getInt(
                     SETTINGS_LATENCY_OBSERVER_SAMPLING_INTERVAL_KEY,
                     BinderLatencyObserver.PERIODIC_SAMPLING_INTERVAL_DEFAULT));
-
+            binderLatencyObserver.setHistogramBucketsParams(
+                    mParser.getInt(
+                        SETTINGS_LATENCY_HISTOGRAM_BUCKET_COUNT_KEY,
+                        BinderLatencyObserver.BUCKET_COUNT_DEFAULT),
+                    mParser.getInt(
+                        SETTINGS_LATENCY_HISTOGRAM_FIRST_BUCKET_SIZE_KEY,
+                        BinderLatencyObserver.FIRST_BUCKET_SIZE_DEFAULT),
+                    mParser.getFloat(
+                        SETTINGS_LATENCY_HISTOGRAM_BUCKET_SCALE_FACTOR_KEY,
+                        BinderLatencyObserver.BUCKET_SCALE_FACTOR_DEFAULT));
+            binderLatencyObserver.setPushInterval(mParser.getInt(
+                    SETTINGS_LATENCY_OBSERVER_PUSH_INTERVAL_MINUTES_KEY,
+                    BinderLatencyObserver.STATSD_PUSH_INTERVAL_MINUTES_DEFAULT));
 
             final boolean enabled =
                     mParser.getBoolean(SETTINGS_ENABLED_KEY, BinderCallsStats.ENABLED_DEFAULT);
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index f591802..8b5e61a 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -3399,48 +3399,6 @@
         }
     }
 
-    @Override
-    public void notifyAppIoBlocked(String volumeUuid, int uid, int tid,
-            @StorageManager.AppIoBlockedReason int reason) {
-        enforceExternalStorageService();
-
-        mStorageSessionController.notifyAppIoBlocked(volumeUuid, uid, tid, reason);
-    }
-
-    @Override
-    public void notifyAppIoResumed(String volumeUuid, int uid, int tid,
-            @StorageManager.AppIoBlockedReason int reason) {
-        enforceExternalStorageService();
-
-        mStorageSessionController.notifyAppIoResumed(volumeUuid, uid, tid, reason);
-    }
-
-    @Override
-    public boolean isAppIoBlocked(String volumeUuid, int uid, int tid,
-            @StorageManager.AppIoBlockedReason int reason) {
-        return isAppIoBlocked(uid);
-    }
-
-
-    private boolean isAppIoBlocked(int uid) {
-        return mStorageSessionController.isAppIoBlocked(uid);
-    }
-
-    /**
-     * Enforces that the caller is the {@link ExternalStorageService}
-     *
-     * @throws SecurityException if the caller doesn't have the
-     * {@link android.Manifest.permission.WRITE_MEDIA_STORAGE} permission or is not the
-     * {@link ExternalStorageService}
-     */
-    private void enforceExternalStorageService() {
-        enforcePermission(android.Manifest.permission.WRITE_MEDIA_STORAGE);
-        int callingAppId = UserHandle.getAppId(Binder.getCallingUid());
-        if (callingAppId != mMediaStoreAuthorityAppId) {
-            throw new SecurityException("Only the ExternalStorageService is permitted");
-        }
-    }
-
     /**
      * Returns PendingIntent which can be used by Apps with MANAGE_EXTERNAL_STORAGE permission
      * to launch the manageSpaceActivity of the App specified by packageName.
@@ -3488,6 +3446,46 @@
             Binder.restoreCallingIdentity(token);
         }
     }
+    
+    @Override
+    public void notifyAppIoBlocked(String volumeUuid, int uid, int tid, int reason) {
+        enforceExternalStorageService();
+
+        mStorageSessionController.notifyAppIoBlocked(volumeUuid, uid, tid, reason);
+    }
+
+    @Override
+    public void notifyAppIoResumed(String volumeUuid, int uid, int tid, int reason) {
+        enforceExternalStorageService();
+
+        mStorageSessionController.notifyAppIoResumed(volumeUuid, uid, tid, reason);
+    }
+
+    @Override
+    public boolean isAppIoBlocked(String volumeUuid, int uid, int tid,
+            @StorageManager.AppIoBlockedReason int reason) {
+        return isAppIoBlocked(uid);
+    }
+
+
+    private boolean isAppIoBlocked(int uid) {
+        return mStorageSessionController.isAppIoBlocked(uid);
+    }
+
+    /**
+     * Enforces that the caller is the {@link ExternalStorageService}
+     *
+     * @throws SecurityException if the caller doesn't have the
+     * {@link android.Manifest.permission.WRITE_MEDIA_STORAGE} permission or is not the
+     * {@link ExternalStorageService}
+     */
+    private void enforceExternalStorageService() {
+        enforcePermission(android.Manifest.permission.WRITE_MEDIA_STORAGE);
+        int callingAppId = UserHandle.getAppId(Binder.getCallingUid());
+        if (callingAppId != mMediaStoreAuthorityAppId) {
+            throw new SecurityException("Only the ExternalStorageService is permitted");
+        }
+    }
 
     /** Not thread safe */
     class AppFuseMountScope extends AppFuseBridge.MountScope {
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index c360190..61d5dac 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -59,11 +59,11 @@
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.PackageManagerInternal;
-import android.content.pm.PackageParser;
 import android.content.pm.RegisteredServicesCache;
 import android.content.pm.RegisteredServicesCacheListener;
 import android.content.pm.ResolveInfo;
 import android.content.pm.Signature;
+import android.content.pm.SigningDetails.CertCapabilities;
 import android.content.pm.UserInfo;
 import android.database.Cursor;
 import android.database.sqlite.SQLiteStatement;
@@ -105,7 +105,6 @@
 import com.android.server.LocalServices;
 import com.android.server.ServiceThread;
 import com.android.server.SystemService;
-import com.android.server.SystemService.TargetUser;
 
 import com.google.android.collect.Lists;
 import com.google.android.collect.Sets;
@@ -4794,9 +4793,7 @@
                 int targetUid = targetActivityInfo.applicationInfo.uid;
                 PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
                 if (!isExportedSystemActivity(targetActivityInfo)
-                        && !pmi.hasSignatureCapability(
-                                targetUid, authUid,
-                                PackageParser.SigningDetails.CertCapabilities.AUTH)) {
+                        && !pmi.hasSignatureCapability(targetUid, authUid, CertCapabilities.AUTH)) {
                     String pkgName = targetActivityInfo.packageName;
                     String activityName = targetActivityInfo.name;
                     String tmpl = "KEY_INTENT resolved to an Activity (%s) in a package (%s) that "
@@ -5549,8 +5546,7 @@
                     return SIGNATURE_CHECK_UID_MATCH;
                 }
                 if (pmi.hasSignatureCapability(
-                        serviceInfo.uid, callingUid,
-                        PackageParser.SigningDetails.CertCapabilities.AUTH)) {
+                        serviceInfo.uid, callingUid, CertCapabilities.AUTH)) {
                     return SIGNATURE_CHECK_MATCH;
                 }
             }
@@ -5591,8 +5587,7 @@
         for (RegisteredServicesCache.ServiceInfo<AuthenticatorDescription> serviceInfo :
                 serviceInfos) {
             if (isOtherwisePermitted || pmi.hasSignatureCapability(
-                    serviceInfo.uid, callingUid,
-                    PackageParser.SigningDetails.CertCapabilities.AUTH)) {
+                    serviceInfo.uid, callingUid, CertCapabilities.AUTH)) {
                 managedAccountTypes.add(serviceInfo.type.type);
             }
         }
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index e022e97..52ab4c8 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -92,6 +92,7 @@
         DeviceConfig.NAMESPACE_STATSD_NATIVE,
         DeviceConfig.NAMESPACE_STATSD_NATIVE_BOOT,
         DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT,
+        DeviceConfig.NAMESPACE_SWCODEC_NATIVE,
         DeviceConfig.NAMESPACE_WINDOW_MANAGER_NATIVE_BOOT,
     };
 
diff --git a/services/core/java/com/android/server/display/color/ColorDisplayService.java b/services/core/java/com/android/server/display/color/ColorDisplayService.java
index 7fa0b21..851accc 100644
--- a/services/core/java/com/android/server/display/color/ColorDisplayService.java
+++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java
@@ -162,7 +162,8 @@
     private final ReduceBrightColorsTintController mReduceBrightColorsTintController =
             new ReduceBrightColorsTintController();
 
-    private final Handler mHandler;
+    @VisibleForTesting
+    final Handler mHandler;
 
     private final AppSaturationController mAppSaturationController = new AppSaturationController();
 
@@ -404,13 +405,13 @@
         // existing activated state. This ensures consistency of tint across the color mode change.
         onDisplayColorModeChanged(getColorModeInternal());
 
+        final DisplayTransformManager dtm = getLocalService(DisplayTransformManager.class);
         if (mNightDisplayTintController.isAvailable(getContext())) {
             // Reset the activated state.
             mNightDisplayTintController.setActivated(null);
 
             // Prepare the night display color transformation matrix.
-            mNightDisplayTintController
-                    .setUp(getContext(), DisplayTransformManager.needsLinearColorMatrix());
+            mNightDisplayTintController.setUp(getContext(), dtm.needsLinearColorMatrix());
             mNightDisplayTintController
                     .setMatrix(mNightDisplayTintController.getColorTemperatureSetting());
 
@@ -432,8 +433,7 @@
         }
 
         if (mReduceBrightColorsTintController.isAvailable(getContext())) {
-            mReduceBrightColorsTintController
-                    .setUp(getContext(), DisplayTransformManager.needsLinearColorMatrix());
+            mReduceBrightColorsTintController.setUp(getContext(), dtm.needsLinearColorMatrix());
             onReduceBrightColorsStrengthLevelChanged();
             final boolean reset = resetReduceBrightColors();
             if (!reset) {
@@ -540,8 +540,8 @@
         mDisplayWhiteBalanceTintController.cancelAnimator();
 
         if (mNightDisplayTintController.isAvailable(getContext())) {
-            mNightDisplayTintController
-                    .setUp(getContext(), DisplayTransformManager.needsLinearColorMatrix(mode));
+            final DisplayTransformManager dtm = getLocalService(DisplayTransformManager.class);
+            mNightDisplayTintController.setUp(getContext(), dtm.needsLinearColorMatrix(mode));
             mNightDisplayTintController
                     .setMatrix(mNightDisplayTintController.getColorTemperatureSetting());
         }
@@ -735,10 +735,11 @@
     @VisibleForTesting
     void updateDisplayWhiteBalanceStatus() {
         boolean oldActivated = mDisplayWhiteBalanceTintController.isActivated();
+        final DisplayTransformManager dtm = getLocalService(DisplayTransformManager.class);
         mDisplayWhiteBalanceTintController.setActivated(isDisplayWhiteBalanceSettingEnabled()
                 && !mNightDisplayTintController.isActivated()
                 && !isAccessibilityEnabled()
-                && DisplayTransformManager.needsLinearColorMatrix());
+                && dtm.needsLinearColorMatrix());
         boolean activated = mDisplayWhiteBalanceTintController.isActivated();
 
         if (mDisplayWhiteBalanceListener != null && oldActivated != activated) {
diff --git a/services/core/java/com/android/server/display/color/DisplayTransformManager.java b/services/core/java/com/android/server/display/color/DisplayTransformManager.java
index 5c68c51..0dba9e1 100644
--- a/services/core/java/com/android/server/display/color/DisplayTransformManager.java
+++ b/services/core/java/com/android/server/display/color/DisplayTransformManager.java
@@ -239,7 +239,7 @@
     /**
      * Return true when the color matrix works in linear space.
      */
-    public static boolean needsLinearColorMatrix() {
+    public boolean needsLinearColorMatrix() {
         return SystemProperties.getInt(PERSISTENT_PROPERTY_DISPLAY_COLOR,
                 DISPLAY_COLOR_UNMANAGED) != DISPLAY_COLOR_UNMANAGED;
     }
@@ -247,7 +247,7 @@
     /**
      * Return true when the specified colorMode requires the color matrix to work in linear space.
      */
-    public static boolean needsLinearColorMatrix(int colorMode) {
+    public boolean needsLinearColorMatrix(int colorMode) {
         return colorMode != ColorDisplayManager.COLOR_MODE_SATURATED;
     }
 
diff --git a/services/core/java/com/android/server/hdmi/ActiveSourceHandler.java b/services/core/java/com/android/server/hdmi/ActiveSourceHandler.java
index 8405bbe..d422d51 100644
--- a/services/core/java/com/android/server/hdmi/ActiveSourceHandler.java
+++ b/services/core/java/com/android/server/hdmi/ActiveSourceHandler.java
@@ -88,7 +88,7 @@
                 tv.updateActiveSource(current, "ActiveSourceHandler");
                 invokeCallback(HdmiControlManager.RESULT_SUCCESS);
             } else {
-                tv.startRoutingControl(newActive.physicalAddress, current.physicalAddress, true,
+                tv.startRoutingControl(newActive.physicalAddress, current.physicalAddress,
                         mCallback);
             }
         }
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecConfig.java b/services/core/java/com/android/server/hdmi/HdmiCecConfig.java
index 6fbb26c..2d66563 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecConfig.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecConfig.java
@@ -35,6 +35,7 @@
 import android.os.UserHandle;
 import android.provider.Settings.Global;
 import android.util.ArrayMap;
+import android.util.Slog;
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
@@ -193,6 +194,7 @@
 
         @Override
         public void onChange(boolean selfChange, Uri uri) {
+            Slog.i(TAG, "SettingsObserver.onChange");
             String setting = uri.getLastPathSegment();
             HdmiCecConfig.this.notifyGlobalSettingChanged(setting);
         }
@@ -619,6 +621,7 @@
     }
 
     private void notifySettingChanged(@NonNull @CecSettingName String name) {
+        Slog.i(TAG, "notifySettingChanged");
         Setting setting = getSetting(name);
         if (setting == null) {
             throw new IllegalArgumentException("Setting '" + name + "' does not exist.");
@@ -654,6 +657,7 @@
         Handler handler = new Handler(looper);
         mSettingsObserver = new SettingsObserver(handler);
         ContentResolver resolver = mContext.getContentResolver();
+        Slog.i(TAG, "registerGlobalSettingsObserver");
         String[] settings = new String[] {
                 Global.HDMI_CONTROL_ENABLED,
                 Global.HDMI_CONTROL_SEND_STANDBY_ON_SLEEP,
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
index 10f6948f..97b64a5 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -136,12 +136,8 @@
         if (!mService.isControlEnabled()) {
             return;
         }
-        if (isActiveSource()) {
-            mService.sendCecCommand(HdmiCecMessageBuilder.buildInactiveSource(
-                    mAddress, mService.getPhysicalAddress()));
-        }
         boolean wasActiveSource = isActiveSource();
-        // Invalidate the internal active source record when goes to standby
+        // Invalidate the internal active source record when going to standby
         mService.setActiveSource(Constants.ADDR_INVALID, Constants.INVALID_PHYSICAL_ADDRESS,
                 "HdmiCecLocalDevicePlayback#onStandby()");
         boolean mTvSendStandbyOnSleep = mService.getHdmiCecConfig().getIntValue(
@@ -167,6 +163,9 @@
                                         Constants.ADDR_BROADCAST));
                         break;
                     case HdmiControlManager.POWER_CONTROL_MODE_NONE:
+                        mService.sendCecCommand(
+                                HdmiCecMessageBuilder.buildInactiveSource(mAddress,
+                                        mService.getPhysicalAddress()));
                         break;
                 }
                 break;
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index acfeb6c..ce5bf08 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -374,12 +374,11 @@
             return;
         }
         int newPath = mService.portIdToPath(portId);
-        startRoutingControl(oldPath, newPath, true, callback);
+        startRoutingControl(oldPath, newPath, callback);
     }
 
     @ServiceThreadOnly
-    void startRoutingControl(int oldPath, int newPath, boolean queryDevicePowerStatus,
-            IHdmiControlCallback callback) {
+    void startRoutingControl(int oldPath, int newPath, IHdmiControlCallback callback) {
         assertRunOnServiceThread();
         if (oldPath == newPath) {
             return;
@@ -389,7 +388,7 @@
         mService.sendCecCommand(routingChange);
         removeAction(RoutingControlAction.class);
         addAndStartAction(
-                new RoutingControlAction(this, newPath, queryDevicePowerStatus, callback));
+                new RoutingControlAction(this, newPath, callback));
     }
 
     @ServiceThreadOnly
@@ -567,7 +566,7 @@
         if (isTailOfActivePath(path, getActivePath())) {
             int newPath = mService.portIdToPath(getActivePortId());
             setActivePath(newPath);
-            startRoutingControl(getActivePath(), newPath, false, null);
+            startRoutingControl(getActivePath(), newPath, null);
             return true;
         }
         return false;
@@ -612,7 +611,7 @@
             getActiveSource().invalidate();
             removeAction(RoutingControlAction.class);
             int newPath = HdmiUtils.twoBytesToInt(params, 2);
-            addAndStartAction(new RoutingControlAction(this, newPath, true, null));
+            addAndStartAction(new RoutingControlAction(this, newPath, null));
         }
         return Constants.HANDLED;
     }
@@ -1189,7 +1188,7 @@
         // Seq #23
         if (isTailOfActivePath(path, getActivePath())) {
             int newPath = mService.portIdToPath(getActivePortId());
-            startRoutingControl(getActivePath(), newPath, true, null);
+            startRoutingControl(getActivePath(), newPath, null);
         }
     }
 
@@ -1207,7 +1206,7 @@
             if (!routingForBootup && !isProhibitMode()) {
                 int newPath = mService.portIdToPath(getActivePortId());
                 setActivePath(newPath);
-                startRoutingControl(getActivePath(), newPath, routingForBootup, null);
+                startRoutingControl(getActivePath(), newPath, null);
             }
         } else {
             int activePath = mService.getPhysicalAddress();
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 031c057..3c5972c 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -2332,6 +2332,7 @@
 
         @Override
         public void setCecSettingStringValue(String name, String value) {
+            Slog.i(TAG, "Setting '" + name + "' to '" + value + "'.");
             initBinderCall();
             long token = Binder.clearCallingIdentity();
             try {
@@ -2354,6 +2355,7 @@
 
         @Override
         public void setCecSettingIntValue(String name, int value) {
+            Slog.i(TAG, "Setting '" + name + "' to '" + value + "'.");
             initBinderCall();
             long token = Binder.clearCallingIdentity();
             try {
@@ -3518,6 +3520,7 @@
             new HdmiCecConfig.SettingChangeListener() {
                 @Override
                 public void onChange(String name) {
+                    Slog.i(TAG, "Change listener fired for '" + name + "' setting.");
                     synchronized (mLock) {
                         if (!mHdmiCecSettingChangeListenerRecords.containsKey(name)) {
                             return;
@@ -3537,6 +3540,7 @@
                 mHdmiCecConfig.registerChangeListener(name, mSettingChangeListener);
             }
             mHdmiCecSettingChangeListenerRecords.get(name).register(listener);
+            Slog.i(TAG, "Added change listener for '" + name + "' setting.");
         }
     }
 
@@ -3550,12 +3554,14 @@
             if (mHdmiCecSettingChangeListenerRecords.get(name).getRegisteredCallbackCount() == 0) {
                 mHdmiCecSettingChangeListenerRecords.remove(name);
                 mHdmiCecConfig.removeChangeListener(name, mSettingChangeListener);
+                Slog.i(TAG, "Removed change listener for '" + name + "' setting.");
             }
         }
     }
 
     private void invokeCecSettingChangeListenerLocked(String name,
             final IHdmiCecSettingChangeListener listener) {
+        Slog.i(TAG, "Listener.onChange() invoked for '" + name + "' setting.");
         try {
             listener.onChange(name);
         } catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/hdmi/RoutingControlAction.java b/services/core/java/com/android/server/hdmi/RoutingControlAction.java
index b9404e4..0c4fb26 100644
--- a/services/core/java/com/android/server/hdmi/RoutingControlAction.java
+++ b/services/core/java/com/android/server/hdmi/RoutingControlAction.java
@@ -17,11 +17,10 @@
 package com.android.server.hdmi;
 
 import android.hardware.hdmi.HdmiControlManager;
-import android.hardware.hdmi.HdmiDeviceInfo;
 import android.hardware.hdmi.IHdmiControlCallback;
 import android.util.Slog;
 
-import com.android.server.hdmi.HdmiControlService.SendMessageCallback;
+import com.android.internal.annotations.VisibleForTesting;
 
 /**
  * Feature action for routing control. Exchanges routing-related commands with other devices
@@ -41,23 +40,12 @@
 
     // State in which we wait for <Routing Information> to arrive. If timed out, we use the
     // latest routing path to set the new active source.
-    private static final int STATE_WAIT_FOR_ROUTING_INFORMATION = 1;
-
-    // State in which we wait for <Report Power Status> in response to <Give Device Power Status>
-    // we have sent. If the response tells us the device power is on, we send <Set Stream Path>
-    // to make it the active source. Otherwise we do not send <Set Stream Path>, and possibly
-    // just show the blank screen.
-    private static final int STATE_WAIT_FOR_REPORT_POWER_STATUS = 2;
+    @VisibleForTesting
+    static final int STATE_WAIT_FOR_ROUTING_INFORMATION = 1;
 
     // Time out in millseconds used for <Routing Information>
     private static final int TIMEOUT_ROUTING_INFORMATION_MS = 1000;
 
-    // Time out in milliseconds used for <Report Power Status>
-    private static final int TIMEOUT_REPORT_POWER_STATUS_MS = 1000;
-
-    // true if <Give Power Status> should be sent once the new active routing path is determined.
-    private final boolean mQueryDevicePowerStatus;
-
     // If set to true, call {@link HdmiControlService#invokeInputChangeListener()} when
     // the routing control/active source change happens. The listener should be called if
     // the events are triggered by external events such as manual switch port change or incoming
@@ -67,11 +55,9 @@
     // The latest routing path. Updated by each <Routing Information> from CEC switches.
     private int mCurrentRoutingPath;
 
-    RoutingControlAction(HdmiCecLocalDevice localDevice, int path, boolean queryDevicePowerStatus,
-            IHdmiControlCallback callback) {
+    RoutingControlAction(HdmiCecLocalDevice localDevice, int path, IHdmiControlCallback callback) {
         super(localDevice, callback);
         mCurrentRoutingPath = path;
-        mQueryDevicePowerStatus = queryDevicePowerStatus;
         // Callback is non-null when routing control action is brought up by binder API. Use
         // this as an indicator for the input change notification. These API calls will get
         // the result through this callback, not through notification. Any other events that
@@ -104,39 +90,16 @@
             removeActionExcept(RoutingControlAction.class, this);
             addTimer(mState, TIMEOUT_ROUTING_INFORMATION_MS);
             return true;
-        } else if (mState == STATE_WAIT_FOR_REPORT_POWER_STATUS
-                  && opcode == Constants.MESSAGE_REPORT_POWER_STATUS) {
-            handleReportPowerStatus(cmd.getParams()[0]);
-            return true;
         }
         return false;
     }
 
-    private void handleReportPowerStatus(int devicePowerStatus) {
-        if (isPowerOnOrTransient(getTvPowerStatus())) {
-            updateActiveInput();
-            if (isPowerOnOrTransient(devicePowerStatus)) {
-                sendSetStreamPath();
-            }
-        }
-        finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
-    }
-
     private void updateActiveInput() {
         HdmiCecLocalDeviceTv tv = tv();
         tv.setPrevPortId(tv.getActivePortId());
         tv.updateActiveInput(mCurrentRoutingPath, mNotifyInputChange);
     }
 
-    private int getTvPowerStatus() {
-        return tv().getPowerStatus();
-    }
-
-    private static boolean isPowerOnOrTransient(int status) {
-        return status == HdmiControlManager.POWER_STATUS_ON
-                || status == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
-    }
-
     private void sendSetStreamPath() {
         sendCommand(HdmiCecMessageBuilder.buildSetStreamPath(getSourceAddress(),
                 mCurrentRoutingPath));
@@ -150,46 +113,13 @@
         }
         switch (timeoutState) {
             case STATE_WAIT_FOR_ROUTING_INFORMATION:
-                HdmiDeviceInfo device =
-                        localDevice().mService.getHdmiCecNetwork().getDeviceInfoByPath(
-                                mCurrentRoutingPath);
-                if (device != null && mQueryDevicePowerStatus) {
-                    int deviceLogicalAddress = device.getLogicalAddress();
-                    queryDevicePowerStatus(deviceLogicalAddress, new SendMessageCallback() {
-                        @Override
-                        public void onSendCompleted(int error) {
-                            handlDevicePowerStatusAckResult(
-                                    error == HdmiControlManager.RESULT_SUCCESS);
-                        }
-                    });
-                } else {
-                    updateActiveInput();
-                    finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
-                }
-                return;
-            case STATE_WAIT_FOR_REPORT_POWER_STATUS:
-                if (isPowerOnOrTransient(getTvPowerStatus())) {
-                    updateActiveInput();
-                    sendSetStreamPath();
-                }
+                updateActiveInput();
+                sendSetStreamPath();
                 finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
                 return;
-        }
-    }
-
-    private void queryDevicePowerStatus(int address, SendMessageCallback callback) {
-        sendCommand(HdmiCecMessageBuilder.buildGiveDevicePowerStatus(getSourceAddress(), address),
-                callback);
-    }
-
-    private void handlDevicePowerStatusAckResult(boolean acked) {
-        if (acked) {
-            mState = STATE_WAIT_FOR_REPORT_POWER_STATUS;
-            addTimer(mState, TIMEOUT_REPORT_POWER_STATUS_MS);
-        } else {
-            updateActiveInput();
-            sendSetStreamPath();
-            finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
+            default:
+                Slog.e("CEC", "Invalid timeoutState (" + timeoutState + ").");
+                return;
         }
     }
 }
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index 864aa33..a9f672f 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -73,7 +73,6 @@
 import android.location.provider.ProviderProperties;
 import android.location.util.identity.CallerIdentity;
 import android.os.Binder;
-import android.os.Build;
 import android.os.Bundle;
 import android.os.ICancellationSignal;
 import android.os.ParcelFileDescriptor;
@@ -180,7 +179,6 @@
             if (phase == PHASE_SYSTEM_SERVICES_READY) {
                 // the location service must be functioning after this boot phase
                 mSystemInjector.onSystemReady();
-                mService.onSystemReady();
             } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
                 // some providers rely on third party code, so we wait to initialize
                 // providers until third party code is allowed to run
@@ -355,24 +353,6 @@
         }
     }
 
-    void onSystemReady() {
-        if (Build.IS_DEBUGGABLE) {
-            // on debug builds, watch for location noteOps while location is off. there are some
-            // scenarios (emergency location) where this is expected, but generally this should
-            // rarely occur, and may indicate bugs. dump occurrences to logs for further evaluation
-            AppOpsManager appOps = Objects.requireNonNull(
-                    mContext.getSystemService(AppOpsManager.class));
-            appOps.startWatchingNoted(
-                    new int[]{AppOpsManager.OP_FINE_LOCATION, AppOpsManager.OP_COARSE_LOCATION},
-                    (code, uid, packageName, attributionTag, flags, result) -> {
-                        if (!isLocationEnabledForUser(UserHandle.getUserId(uid))) {
-                            Log.w(TAG, "location noteOp with location off - "
-                                    + CallerIdentity.forTest(uid, 0, packageName, attributionTag));
-                        }
-                    });
-        }
-    }
-
     void onSystemThirdPartyAppsCanStart() {
         // network provider should always be initialized before the gps provider since the gps
         // provider has unfortunate hard dependencies on the network provider
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 5bcda410..110a3db 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -2178,14 +2178,16 @@
 
         mUserProfiles.updateCache(getContext());
 
-        telephonyManager.listen(new PhoneStateListener() {
-            @Override
-            public void onCallStateChanged(int state, String incomingNumber) {
-                if (mCallState == state) return;
-                if (DBG) Slog.d(TAG, "Call state changed: " + callStateToString(state));
-                mCallState = state;
-            }
-        }, PhoneStateListener.LISTEN_CALL_STATE);
+        if (mPackageManagerClient.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            telephonyManager.listen(new PhoneStateListener() {
+                @Override
+                public void onCallStateChanged(int state, String incomingNumber) {
+                    if (mCallState == state) return;
+                    if (DBG) Slog.d(TAG, "Call state changed: " + callStateToString(state));
+                    mCallState = state;
+                }
+            }, PhoneStateListener.LISTEN_CALL_STATE);
+        }
 
         mSettingsObserver = new SettingsObserver(mHandler);
 
diff --git a/services/core/java/com/android/server/pm/ApkChecksums.java b/services/core/java/com/android/server/pm/ApkChecksums.java
index afce23f..a14058b 100644
--- a/services/core/java/com/android/server/pm/ApkChecksums.java
+++ b/services/core/java/com/android/server/pm/ApkChecksums.java
@@ -37,6 +37,7 @@
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.PackageParser;
 import android.content.pm.Signature;
+import android.content.pm.SigningDetails.SignatureSchemeVersion;
 import android.content.pm.parsing.ApkLiteParseUtils;
 import android.os.Handler;
 import android.os.RemoteException;
@@ -461,8 +462,8 @@
                 }
 
                 // Obtaining array of certificates used for signing the installer package.
-                certs = installer.getSigningDetails().signatures;
-                pastCerts = installer.getSigningDetails().pastSigningCertificates;
+                certs = installer.getSigningDetails().getSignatures();
+                pastCerts = installer.getSigningDetails().getPastSigningCertificates();
             }
             if (certs == null || certs.length == 0 || certs[0] == null) {
                 Slog.e(TAG, "Can't obtain certificates.");
@@ -664,8 +665,7 @@
         Map<Integer, byte[]> contentDigests = null;
         try {
             contentDigests = ApkSignatureVerifier.verifySignaturesInternal(filePath,
-                    PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V2,
-                    false).contentDigests;
+                    SignatureSchemeVersion.SIGNING_BLOCK_V2, /* verifyFull */ false).contentDigests;
         } catch (PackageParser.PackageParserException e) {
             if (!(e.getCause() instanceof SignatureNotFoundException)) {
                 Slog.e(TAG, "Signature verification error", e);
diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java
index ca8202f..d7711d6 100644
--- a/services/core/java/com/android/server/pm/AppsFilter.java
+++ b/services/core/java/com/android/server/pm/AppsFilter.java
@@ -28,7 +28,7 @@
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
-import android.content.pm.PackageParser;
+import android.content.pm.SigningDetails;
 import android.content.pm.UserInfo;
 import android.content.pm.parsing.component.ParsedComponent;
 import android.content.pm.parsing.component.ParsedInstrumentation;
@@ -143,7 +143,7 @@
     private final OverlayReferenceMapper mOverlayReferenceMapper;
     private final StateProvider mStateProvider;
 
-    private PackageParser.SigningDetails mSystemSigningDetails;
+    private SigningDetails mSystemSigningDetails;
     private Set<String> mProtectedBroadcasts = new ArraySet<>();
 
     private final Object mCacheLock = new Object();
@@ -914,7 +914,7 @@
         }
     }
 
-    private static boolean isSystemSigned(@NonNull PackageParser.SigningDetails sysSigningDetails,
+    private static boolean isSystemSigned(@NonNull SigningDetails sysSigningDetails,
             PackageSetting pkgSetting) {
         return pkgSetting.isSystem()
                 && pkgSetting.signatures.mSigningDetails.signaturesMatchExactly(sysSigningDetails);
diff --git a/services/core/java/com/android/server/pm/InstantAppRegistry.java b/services/core/java/com/android/server/pm/InstantAppRegistry.java
index 7bf7042..e09bcf0 100644
--- a/services/core/java/com/android/server/pm/InstantAppRegistry.java
+++ b/services/core/java/com/android/server/pm/InstantAppRegistry.java
@@ -23,8 +23,8 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.InstantAppInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageParser;
 import android.content.pm.PermissionInfo;
+import android.content.pm.SigningDetails;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.graphics.Canvas;
@@ -405,14 +405,14 @@
             // into account but also allow the value from the old computation to avoid
             // data loss.
             if (pkg.getSigningDetails().checkCapability(currentCookieSha256,
-                    PackageParser.SigningDetails.CertCapabilities.INSTALLED_DATA)) {
+                    SigningDetails.CertCapabilities.INSTALLED_DATA)) {
                 return;
             }
 
             // For backwards compatibility we accept match based on any signature, since we may have
             // recorded only the first for multiply-signed packages
-            final String[] signaturesSha256Digests =
-                    PackageUtils.computeSignaturesSha256Digests(pkg.getSigningDetails().signatures);
+            final String[] signaturesSha256Digests = PackageUtils.computeSignaturesSha256Digests(
+                    pkg.getSigningDetails().getSignatures());
             for (String s : signaturesSha256Digests) {
                 if (s.equals(currentCookieSha256)) {
                     return;
@@ -1298,8 +1298,8 @@
             // We prefer the modern computation procedure where all certs are taken
             // into account and delete the file derived via the legacy hash computation.
             File newCookieFile = computeInstantCookieFile(pkg.getPackageName(),
-                    PackageUtils.computeSignaturesSha256Digest(pkg.getSigningDetails().signatures),
-                    userId);
+                    PackageUtils.computeSignaturesSha256Digest(
+                            pkg.getSigningDetails().getSignatures()), userId);
             if (!pkg.getSigningDetails().hasSignatures()) {
                 Slog.wtf(LOG_TAG, "Parsed Instant App contains no valid signatures!");
             }
diff --git a/services/core/java/com/android/server/pm/KeySetManagerService.java b/services/core/java/com/android/server/pm/KeySetManagerService.java
index 2015c78..9853263 100644
--- a/services/core/java/com/android/server/pm/KeySetManagerService.java
+++ b/services/core/java/com/android/server/pm/KeySetManagerService.java
@@ -192,7 +192,7 @@
             throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
                     "Passed invalid package to keyset validation.");
         }
-        ArraySet<PublicKey> signingKeys = pkg.getSigningDetails().publicKeys;
+        ArraySet<PublicKey> signingKeys = pkg.getSigningDetails().getPublicKeys();
         if (signingKeys == null || !(signingKeys.size() > 0) || signingKeys.contains(null)) {
             throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
                     "Package has invalid signing-key-set.");
@@ -225,7 +225,7 @@
         PackageSetting ps = mPackages.get(pkg.getPackageName());
         Objects.requireNonNull(ps, "pkg: " + pkg.getPackageName()
                     + "does not have a corresponding entry in mPackages.");
-        addSigningKeySetToPackageLPw(ps, pkg.getSigningDetails().publicKeys);
+        addSigningKeySetToPackageLPw(ps, pkg.getSigningDetails().getPublicKeys());
         if (pkg.getKeySetMapping() != null) {
             addDefinedKeySetsToPackageLPw(ps, pkg.getKeySetMapping());
             if (pkg.getUpgradeKeySets() != null) {
@@ -370,7 +370,7 @@
         for (int i = 0; i < upgradeKeySets.length; i++) {
             Set<PublicKey> upgradeSet = getPublicKeysFromKeySetLPr(upgradeKeySets[i]);
             if (upgradeSet != null
-                    && pkg.getSigningDetails().publicKeys.containsAll(upgradeSet)) {
+                    && pkg.getSigningDetails().getPublicKeys().containsAll(upgradeSet)) {
                 return true;
             }
         }
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index e532790..af7896e 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -82,8 +82,8 @@
 import android.content.pm.PackageInstaller.SessionParams;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
-import android.content.pm.PackageParser;
 import android.content.pm.PackageParser.PackageParserException;
+import android.content.pm.SigningDetails;
 import android.content.pm.dex.DexMetadataHelper;
 import android.content.pm.parsing.ApkLite;
 import android.content.pm.parsing.ApkLiteParseUtils;
@@ -364,7 +364,7 @@
     @GuardedBy("mLock")
     private long mVersionCode;
     @GuardedBy("mLock")
-    private PackageParser.SigningDetails mSigningDetails;
+    private SigningDetails mSigningDetails;
     @GuardedBy("mLock")
     private SparseArray<PackageInstallerSession> mChildSessions = new SparseArray<>();
     @GuardedBy("mLock")
@@ -703,28 +703,22 @@
         }
 
         /**
-         * Notified by the staging manager that pre-reboot verification is about to start. The
-         * return value should be checked to decide whether it is OK to start pre-reboot
-         * verification. In the case of a destroyed session, {@code false} is returned and there is
-         * no need to start pre-reboot verification.
+         * Called when pre-reboot verification is about to start. This shouldn't be called
+         * on a destroyed session.
          */
-        @Override
-        public boolean notifyStartPreRebootVerification() {
+        private void notifyStartPreRebootVerification() {
             synchronized (mLock) {
+                Preconditions.checkState(!mDestroyed);
                 if (mInPreRebootVerification) {
                     throw new IllegalStateException("Pre-reboot verification has started");
                 }
-                if (mDestroyed) {
-                    return false;
-                }
                 mInPreRebootVerification = true;
-                return true;
             }
         }
 
         /**
-         * Notified by the staging manager that pre-reboot verification has ended. Now it is safe to
-         * clean up the session if {@link #abandon()} has been called previously.
+         * Notified by the staging manager or PIS that pre-reboot verification has ended.
+         * Now it is safe to clean up the session if {@link #abandon()} has been called previously.
          */
         @Override
         public void notifyEndPreRebootVerification() {
@@ -748,6 +742,7 @@
             assertCallerIsOwnerOrRootOrSystem();
             Preconditions.checkArgument(isCommitted());
             Preconditions.checkArgument(!mSessionApplied && !mSessionFailed);
+            notifyStartPreRebootVerification();
             verify();
         }
 
@@ -2092,9 +2087,7 @@
         if (isStaged()) {
             mStagedSession.setSessionFailed(
                     SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, msgWithErrorCode);
-            // TODO(b/136257624): Remove this once all verification logic has been transferred out
-            //  of StagingManager.
-            mStagingManager.notifyVerificationComplete(mStagedSession);
+            mStagedSession.notifyEndPreRebootVerification();
         } else {
             // Dispatch message to remove session from PackageInstallerService.
             dispatchSessionFinished(error, msg, null);
@@ -2214,20 +2207,14 @@
                     .write();
         }
         if (params.isStaged) {
-            mStagingManager.commitSession(mStagedSession);
             // TODO(b/136257624): CTS test fails if we don't send session finished broadcast, even
             //  though ideally, we just need to send session committed broadcast.
             dispatchSessionFinished(INSTALL_SUCCEEDED, "Session staged", null);
-            return;
-        }
 
-        if (isApexSession()) {
-            destroyInternal();
-            dispatchSessionFinished(PackageManager.INSTALL_FAILED_INTERNAL_ERROR,
-                    "APEX packages can only be installed using staged sessions.", null);
-            return;
+            mStagedSession.verifySession();
+        } else {
+            verify();
         }
-        verify();
     }
 
     private void verify() {
@@ -2377,18 +2364,20 @@
             PackageLite result = parseApkLite();
             if (result != null) {
                 mPackageLite = result;
-                synchronized (mProgressLock) {
-                    mInternalProgress = 0.5f;
-                    computeProgressLocked(true);
-                }
+                if (!isApexSession()) {
+                    synchronized (mProgressLock) {
+                        mInternalProgress = 0.5f;
+                        computeProgressLocked(true);
+                    }
 
-                extractNativeLibraries(
-                        mPackageLite, stageDir, params.abiOverride, mayInheritNativeLibs());
+                    extractNativeLibraries(
+                            mPackageLite, stageDir, params.abiOverride, mayInheritNativeLibs());
 
-                if (userActionRequirement == USER_ACTION_PENDING_APK_PARSING
-                        && (result.getTargetSdk() < Build.VERSION_CODES.Q)) {
-                    sendPendingUserActionIntent();
-                    return null;
+                    if (userActionRequirement == USER_ACTION_PENDING_APK_PARSING
+                            && (result.getTargetSdk() < Build.VERSION_CODES.Q)) {
+                        sendPendingUserActionIntent();
+                        return null;
+                    }
                 }
             }
             return makeVerificationParamsLocked();
@@ -2421,16 +2410,14 @@
     private PackageLite parseApkLite() throws PackageManagerException {
 
 
-        // TODO(b/136257624): Some logic in this if block probably belongs in
-        //  makeInstallParams().
-        if (!isMultiPackage() && !isApexSession()) {
+        if (!isMultiPackage()) {
             Objects.requireNonNull(mPackageName);
             Objects.requireNonNull(mSigningDetails);
             Objects.requireNonNull(mResolvedBaseFile);
 
-            // If we haven't already parsed, inherit any packages and native libraries from existing
-            // install that haven't been overridden.
-            if (params.mode == SessionParams.MODE_INHERIT_EXISTING) {
+            // Inherit any packages and native libraries from existing install that
+            // haven't been overridden.
+            if (!isApexSession() && params.mode == SessionParams.MODE_INHERIT_EXISTING) {
                 try {
                     final List<File> fromFiles = mResolvedInheritedFiles;
                     final File toDir = stageDir;
@@ -2477,11 +2464,16 @@
                             "Failed to inherit existing install", e);
                 }
             }
-            // For mode inherit existing, it would link/copy existing files to stage dir in the
-            // above block. Therefore, we need to parse the complete package in stage dir here.
-            // Besides, PackageLite may be null for staged sessions that don't complete pre-reboot
-            // verification.
-            return getOrParsePackageLiteLocked(stageDir, /* flags */ 0);
+
+            if (!isApexSession()) {
+                // For mode inherit existing, it would link/copy existing files to stage dir in the
+                // above block. Therefore, we need to parse the complete package in stage dir here.
+                // Besides, PackageLite may be null for staged sessions that don't complete
+                // pre-reboot verification.
+                return getOrParsePackageLiteLocked(stageDir, /* flags */ 0);
+            } else {
+                return getOrParsePackageLiteLocked(mResolvedBaseFile, /* flags */ 0);
+            }
         }
         return null;
     }
@@ -2536,16 +2528,17 @@
     }
 
     private void onVerificationComplete() {
-        // Staged sessions will be installed later during boot
+        // APK verification is done. Continue the installation depending on whether it is a
+        // staged session or not. For a staged session, we will hand it over to the staging
+        // manager to complete the installation.
         if (isStaged()) {
-            // TODO(b/136257624): Remove this once all verification logic has been transferred out
-            //  of StagingManager.
-            mStagingManager.notifyPreRebootVerification_Apk_Complete(mStagedSession);
-            // TODO(b/136257624): We also need to destroy internals for verified staged session,
-            //  otherwise file descriptors are never closed for verified staged session until reboot
+            mStagingManager.commitSession(mStagedSession);
             return;
         }
 
+        // APEX sessions should be handled above
+        Preconditions.checkState(!isApexSession());
+
         install();
     }
 
@@ -2778,6 +2771,8 @@
             mPackageName = apk.getPackageName();
             mVersionCode = apk.getLongVersionCode();
         }
+
+        mSigningDetails = apk.getSigningDetails();
     }
 
     /**
@@ -2800,7 +2795,7 @@
         mPackageLite = null;
         mPackageName = null;
         mVersionCode = -1;
-        mSigningDetails = PackageParser.SigningDetails.UNKNOWN;
+        mSigningDetails = SigningDetails.UNKNOWN;
 
         mResolvedBaseFile = null;
         mResolvedStagedFiles.clear();
@@ -2863,7 +2858,7 @@
                 mPackageName = apk.getPackageName();
                 mVersionCode = apk.getLongVersionCode();
             }
-            if (mSigningDetails == PackageParser.SigningDetails.UNKNOWN) {
+            if (mSigningDetails == SigningDetails.UNKNOWN) {
                 mSigningDetails = apk.getSigningDetails();
             }
 
@@ -2918,7 +2913,7 @@
                 mPackageName = pkgInfo.packageName;
                 mVersionCode = pkgInfo.getLongVersionCode();
             }
-            if (mSigningDetails == PackageParser.SigningDetails.UNKNOWN) {
+            if (mSigningDetails == SigningDetails.UNKNOWN) {
                 mSigningDetails = unsafeGetCertsWithoutVerification(
                         pkgInfo.applicationInfo.sourceDir);
             }
@@ -2978,7 +2973,7 @@
             packageLite = existing;
             assertPackageConsistentLocked("Existing", existing.getPackageName(),
                     existing.getLongVersionCode());
-            final PackageParser.SigningDetails signingDetails =
+            final SigningDetails signingDetails =
                     unsafeGetCertsWithoutVerification(existing.getBaseApkPath());
             if (!mSigningDetails.signaturesMatchExactly(signingDetails)) {
                 throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
@@ -3318,11 +3313,11 @@
         }
     }
 
-    private PackageParser.SigningDetails unsafeGetCertsWithoutVerification(String path)
+    private SigningDetails unsafeGetCertsWithoutVerification(String path)
             throws PackageManagerException {
         try {
             return ApkSignatureVerifier.unsafeGetCertsWithoutVerification(path,
-                    PackageParser.SigningDetails.SignatureSchemeVersion.JAR);
+                    SigningDetails.SignatureSchemeVersion.JAR);
         } catch (PackageParserException e) {
             throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
                     "Couldn't obtain signatures from APK : " + path);
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 321c5ca..6b5006a 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -95,7 +95,7 @@
 import static android.content.pm.PackageManager.TYPE_UNKNOWN;
 import static android.content.pm.PackageManager.UNINSTALL_REASON_UNKNOWN;
 import static android.content.pm.PackageManagerInternal.LAST_KNOWN_PACKAGE;
-import static android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V4;
+import static android.content.pm.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V4;
 import static android.content.pm.parsing.ApkLiteParseUtils.isApkFile;
 import static android.os.PowerWhitelistManager.REASON_LOCKED_BOOT_COMPLETED;
 import static android.os.PowerWhitelistManager.REASON_PACKAGE_REPLACED;
@@ -149,6 +149,7 @@
 import android.app.ApplicationPackageManager;
 import android.app.BroadcastOptions;
 import android.app.IActivityManager;
+import android.app.PendingIntent;
 import android.app.ResourcesManager;
 import android.app.admin.IDevicePolicyManager;
 import android.app.admin.SecurityLog;
@@ -161,6 +162,7 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.IIntentReceiver;
+import android.content.IIntentSender;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.IntentSender;
@@ -212,8 +214,6 @@
 import android.content.pm.PackageManagerInternal.PrivateResolveFlags;
 import android.content.pm.PackageParser;
 import android.content.pm.PackageParser.PackageParserException;
-import android.content.pm.PackageParser.SigningDetails;
-import android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion;
 import android.content.pm.PackagePartitions;
 import android.content.pm.PackagePartitions.SystemPartition;
 import android.content.pm.PackageStats;
@@ -228,6 +228,8 @@
 import android.content.pm.ServiceInfo;
 import android.content.pm.SharedLibraryInfo;
 import android.content.pm.Signature;
+import android.content.pm.SigningDetails;
+import android.content.pm.SigningDetails.SignatureSchemeVersion;
 import android.content.pm.SigningInfo;
 import android.content.pm.SuspendDialogInfo;
 import android.content.pm.TestUtilityService;
@@ -9109,7 +9111,8 @@
         if (p2SigningDetails == null) {
             return PackageManager.SIGNATURE_SECOND_NOT_SIGNED;
         }
-        int result = compareSignatures(p1SigningDetails.signatures, p2SigningDetails.signatures);
+        int result = compareSignatures(p1SigningDetails.getSignatures(),
+                p2SigningDetails.getSignatures());
         if (result == PackageManager.SIGNATURE_MATCH) {
             return result;
         }
@@ -9119,11 +9122,11 @@
         if (p1SigningDetails.hasPastSigningCertificates()
                 || p2SigningDetails.hasPastSigningCertificates()) {
             Signature[] p1Signatures = p1SigningDetails.hasPastSigningCertificates()
-                    ? new Signature[]{p1SigningDetails.pastSigningCertificates[0]}
-                    : p1SigningDetails.signatures;
+                    ? new Signature[]{p1SigningDetails.getPastSigningCertificates()[0]}
+                    : p1SigningDetails.getSignatures();
             Signature[] p2Signatures = p2SigningDetails.hasPastSigningCertificates()
-                    ? new Signature[]{p2SigningDetails.pastSigningCertificates[0]}
-                    : p2SigningDetails.signatures;
+                    ? new Signature[]{p2SigningDetails.getPastSigningCertificates()[0]}
+                    : p2SigningDetails.getSignatures();
             result = compareSignatures(p1Signatures, p2Signatures);
         }
         return result;
@@ -9167,7 +9170,7 @@
         final int appId = UserHandle.getAppId(uid);
         // reader
         synchronized (mLock) {
-            final PackageParser.SigningDetails signingDetails;
+            final SigningDetails signingDetails;
             final Object obj = mSettings.getSettingLPr(appId);
             if (obj != null) {
                 if (obj instanceof SharedUserSetting) {
@@ -11328,14 +11331,14 @@
                 && ps.timeStamp == lastModifiedTime
                 && !isCompatSignatureUpdateNeeded(settingsVersionForPackage)
                 && !isRecoverSignatureUpdateNeeded(settingsVersionForPackage)) {
-            if (ps.signatures.mSigningDetails.signatures != null
-                    && ps.signatures.mSigningDetails.signatures.length != 0
-                    && ps.signatures.mSigningDetails.signatureSchemeVersion
+            if (ps.signatures.mSigningDetails.getSignatures() != null
+                    && ps.signatures.mSigningDetails.getSignatures().length != 0
+                    && ps.signatures.mSigningDetails.getSignatureSchemeVersion()
                             != SignatureSchemeVersion.UNKNOWN) {
                 // Optimization: reuse the existing cached signing data
                 // if the package appears to be unchanged.
                 parsedPackage.setSigningDetails(
-                        new PackageParser.SigningDetails(ps.signatures.mSigningDetails));
+                        new SigningDetails(ps.signatures.mSigningDetails));
                 return;
             }
 
@@ -11639,10 +11642,10 @@
 
             if (!parsedPackage.getSigningDetails()
                     .checkCapability(pkgSetting.signatures.mSigningDetails,
-                    PackageParser.SigningDetails.CertCapabilities.INSTALLED_DATA)
+                    SigningDetails.CertCapabilities.INSTALLED_DATA)
                             && !pkgSetting.signatures.mSigningDetails.checkCapability(
                                     parsedPackage.getSigningDetails(),
-                                    PackageParser.SigningDetails.CertCapabilities.ROLLBACK)) {
+                                    SigningDetails.CertCapabilities.ROLLBACK)) {
                 logCriticalInfo(Log.WARN,
                         "System package signature mismatch;"
                         + " name: " + pkgSetting.name);
@@ -12972,9 +12975,9 @@
                         // For apps targeting O MR1 we require explicit enumeration of all certs.
                         final String[] libCertDigests = (targetSdk >= Build.VERSION_CODES.O_MR1)
                                 ? PackageUtils.computeSignaturesSha256Digests(
-                                libPkg.signatures)
+                                libPkg.getSignatures())
                                 : PackageUtils.computeSignaturesSha256Digests(
-                                        new Signature[]{libPkg.signatures[0]});
+                                        new Signature[]{libPkg.getSignatures()[0]});
 
                         // Take a shortcut if sizes don't match. Note that if an app doesn't
                         // target O we don't parse the "additional-certificate" tags similarly
@@ -13315,8 +13318,9 @@
                 // priv-apps.
                 synchronized (mLock) {
                     PackageSetting platformPkgSetting = mSettings.getPackageLPr("android");
-                    if ((compareSignatures(platformPkgSetting.signatures.mSigningDetails.signatures,
-                            pkg.getSigningDetails().signatures)
+                    if ((compareSignatures(
+                            platformPkgSetting.signatures.mSigningDetails.getSignatures(),
+                            pkg.getSigningDetails().getSignatures())
                             != PackageManager.SIGNATURE_MATCH)) {
                         scanFlags |= SCAN_AS_PRIVILEGED;
                     }
@@ -14155,8 +14159,8 @@
         parsedPackage.setSignedWithPlatformKey(
                 (PLATFORM_PACKAGE_NAME.equals(parsedPackage.getPackageName())
                         || (platformPkg != null && compareSignatures(
-                        platformPkg.getSigningDetails().signatures,
-                        parsedPackage.getSigningDetails().signatures
+                        platformPkg.getSigningDetails().getSignatures(),
+                        parsedPackage.getSigningDetails().getSignatures()
                 ) == PackageManager.SIGNATURE_MATCH))
         );
 
@@ -14452,7 +14456,7 @@
                     // Exempt SharedUsers signed with the platform key.
                     PackageSetting platformPkgSetting = mSettings.getPackageLPr("android");
                     if (!comparePackageSignatures(platformPkgSetting,
-                            pkg.getSigningDetails().signatures)) {
+                            pkg.getSigningDetails().getSignatures())) {
                         throw new PackageManagerException("Apps that share a user with a " +
                                 "privileged app must themselves be marked as privileged. " +
                                 pkg.getPackageName() + " shares privileged user " +
@@ -14500,7 +14504,7 @@
                         final PackageSetting platformPkgSetting =
                                 mSettings.getPackageLPr("android");
                         if (!comparePackageSignatures(platformPkgSetting,
-                                pkg.getSigningDetails().signatures)) {
+                                pkg.getSigningDetails().getSignatures())) {
                             throw new PackageManagerException("Overlay "
                                     + pkg.getPackageName()
                                     + " must target Q or later, "
@@ -14520,7 +14524,7 @@
                                 mSettings.getPackageLPr(pkg.getOverlayTarget());
                         if (targetPkgSetting != null) {
                             if (!comparePackageSignatures(targetPkgSetting,
-                                    pkg.getSigningDetails().signatures)) {
+                                    pkg.getSigningDetails().getSignatures())) {
                                 // check reference signature
                                 if (mOverlayConfigSignaturePackage == null) {
                                     throw new PackageManagerException("Overlay "
@@ -14532,7 +14536,7 @@
                                 final PackageSetting refPkgSetting =
                                         mSettings.getPackageLPr(mOverlayConfigSignaturePackage);
                                 if (!comparePackageSignatures(refPkgSetting,
-                                        pkg.getSigningDetails().signatures)) {
+                                        pkg.getSigningDetails().getSignatures())) {
                                     throw new PackageManagerException("Overlay "
                                             + pkg.getPackageName() + " signed with a different "
                                             + "certificate than both the reference package and "
@@ -14551,7 +14555,8 @@
                 int minSignatureSchemeVersion =
                         ApkSignatureVerifier.getMinimumSignatureSchemeVersionForTargetSdk(
                                 pkg.getTargetSdkVersion());
-                if (pkg.getSigningDetails().signatureSchemeVersion < minSignatureSchemeVersion) {
+                if (pkg.getSigningDetails().getSignatureSchemeVersion()
+                        < minSignatureSchemeVersion) {
                     throw new PackageManagerException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
                             "No signature found in package of version " + minSignatureSchemeVersion
                                     + " or newer for package " + pkg.getPackageName());
@@ -16359,7 +16364,7 @@
             final AndroidPackage pkg = mPackages.get(verifierInfo.packageName);
             if (pkg == null) {
                 return -1;
-            } else if (pkg.getSigningDetails().signatures.length != 1) {
+            } else if (pkg.getSigningDetails().getSignatures().length != 1) {
                 Slog.i(TAG, "Verifier package " + verifierInfo.packageName
                         + " has more than one signature; ignoring");
                 return -1;
@@ -16373,7 +16378,7 @@
 
             final byte[] expectedPublicKey;
             try {
-                final Signature verifierSig = pkg.getSigningDetails().signatures[0];
+                final Signature verifierSig = pkg.getSigningDetails().getSignatures()[0];
                 final PublicKey publicKey = verifierSig.getPublicKey();
                 expectedPublicKey = publicKey.getEncoded();
             } catch (CertificateException e) {
@@ -16628,9 +16633,10 @@
             if (obj != null) {
                 if (obj instanceof SharedUserSetting) {
                     callerSignature =
-                            ((SharedUserSetting)obj).signatures.mSigningDetails.signatures;
+                            ((SharedUserSetting) obj).signatures.mSigningDetails.getSignatures();
                 } else if (obj instanceof PackageSetting) {
-                    callerSignature = ((PackageSetting)obj).signatures.mSigningDetails.signatures;
+                    callerSignature =
+                            ((PackageSetting) obj).signatures.mSigningDetails.getSignatures();
                 } else {
                     throw new SecurityException("Bad object " + obj + " for uid " + callingUid);
                 }
@@ -16642,7 +16648,7 @@
             // not signed with the same cert as the caller.
             if (installerPackageSetting != null) {
                 if (compareSignatures(callerSignature,
-                        installerPackageSetting.signatures.mSigningDetails.signatures)
+                        installerPackageSetting.signatures.mSigningDetails.getSignatures())
                         != PackageManager.SIGNATURE_MATCH) {
                     throw new SecurityException(
                             "Caller does not have same cert as new installer package "
@@ -16659,7 +16665,7 @@
 
             if (targetInstallerPkgSetting != null) {
                 if (compareSignatures(callerSignature,
-                        targetInstallerPkgSetting.signatures.mSigningDetails.signatures)
+                        targetInstallerPkgSetting.signatures.mSigningDetails.getSignatures())
                         != PackageManager.SIGNATURE_MATCH) {
                     throw new SecurityException(
                             "Caller does not have same cert as old installer package "
@@ -17158,7 +17164,7 @@
         final String[] grantedRuntimePermissions;
         final List<String> whitelistedRestrictedPermissions;
         final int autoRevokePermissionsMode;
-        final PackageParser.SigningDetails signingDetails;
+        final SigningDetails signingDetails;
         final int installReason;
         final int mInstallScenario;
         @Nullable MultiPackageInstallParams mParentInstallParams;
@@ -17182,7 +17188,7 @@
             this.grantedRuntimePermissions = null;
             this.whitelistedRestrictedPermissions = null;
             this.autoRevokePermissionsMode = MODE_DEFAULT;
-            this.signingDetails = PackageParser.SigningDetails.UNKNOWN;
+            this.signingDetails = SigningDetails.UNKNOWN;
             this.installReason = PackageManager.INSTALL_REASON_UNKNOWN;
             this.mInstallScenario = PackageManager.INSTALL_SCENARIO_DEFAULT;
             this.forceQueryableOverride = false;
@@ -17470,7 +17476,7 @@
         @NonNull final InstallSource installSource;
         final String packageAbiOverride;
         final VerificationInfo verificationInfo;
-        final PackageParser.SigningDetails signingDetails;
+        final SigningDetails signingDetails;
         @Nullable MultiPackageVerificationParams mParentVerificationParams;
         final long requiredInstalledVersionCode;
         final int mDataLoaderType;
@@ -17513,13 +17519,6 @@
         }
 
         public void handleStartCopy() {
-            if ((installFlags & PackageManager.INSTALL_APEX) != 0) {
-                // Apex packages get verified in StagingManager currently.
-                // TODO(b/136257624): Move apex verification logic out of StagingManager
-                mRet = INSTALL_SUCCEEDED;
-                return;
-            }
-
             PackageInfoLite pkgLite = PackageManagerServiceUtils.getMinimalPackageInfo(mContext,
                     mPackageLite, origin.resolvedPath, installFlags, packageAbiOverride);
 
@@ -17531,7 +17530,10 @@
             // Perform package verification and enable rollback (unless we are simply moving the
             // package).
             if (!origin.existing) {
-                sendApkVerificationRequest(pkgLite);
+                if ((installFlags & PackageManager.INSTALL_APEX) == 0) {
+                    // TODO(b/182426975): treat APEX as APK when APK verification is concerned
+                    sendApkVerificationRequest(pkgLite);
+                }
                 if ((installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0) {
                     sendEnableRollbackRequest();
                 }
@@ -17692,7 +17694,7 @@
             final boolean isVerificationEnabled = isVerificationEnabled(
                     pkgLite, verifierUser.getIdentifier(), installFlags, installerUid);
             final boolean isV4Signed =
-                    (signingDetails.signatureSchemeVersion == SIGNING_BLOCK_V4);
+                    (signingDetails.getSignatureSchemeVersion() == SIGNING_BLOCK_V4);
             final boolean isIncrementalInstall =
                     (mDataLoaderType == DataLoaderType.INCREMENTAL);
             // NOTE: We purposefully skip verification for only incremental installs when there's
@@ -17923,7 +17925,7 @@
         /** If non-null, drop an async trace when the install completes */
         final String traceMethod;
         final int traceCookie;
-        final PackageParser.SigningDetails signingDetails;
+        final SigningDetails signingDetails;
         final int installReason;
         final int mInstallScenario;
         final boolean forceQueryableOverride;
@@ -18061,7 +18063,7 @@
         FileInstallArgs(String codePath, String[] instructionSets) {
             super(OriginInfo.fromNothing(), null, null, 0, InstallSource.EMPTY,
                     null, null, instructionSets, null, null, null, MODE_DEFAULT, null, 0,
-                    PackageParser.SigningDetails.UNKNOWN,
+                    SigningDetails.UNKNOWN,
                     PackageManager.INSTALL_REASON_UNKNOWN, PackageManager.INSTALL_SCENARIO_DEFAULT,
                     false, DataLoaderType.NONE);
             this.codeFile = (codePath != null) ? new File(codePath) : null;
@@ -18927,11 +18929,11 @@
                     // the signatures on the first package scanned for the shared user (i.e. if the
                     // signaturesChanged state hasn't been initialized yet in SharedUserSetting).
                     if (signatureCheckPs.sharedUser != null) {
-                        final Signature[] sharedUserSignatures =
-                                signatureCheckPs.sharedUser.signatures.mSigningDetails.signatures;
+                        final Signature[] sharedUserSignatures = signatureCheckPs.sharedUser
+                                .signatures.mSigningDetails.getSignatures();
                         if (signatureCheckPs.sharedUser.signaturesChanged != null
                                 && compareSignatures(sharedUserSignatures,
-                                parsedPackage.getSigningDetails().signatures)
+                                parsedPackage.getSigningDetails().getSignatures())
                                         != PackageManager.SIGNATURE_MATCH) {
                             if (SystemProperties.getInt("ro.product.first_api_level", 0) <= 29) {
                                 // Mismatched signatures is an error and silently skipping system
@@ -19335,7 +19337,7 @@
                     if (args.mDataLoaderType != DataLoaderType.INCREMENTAL) {
                         continue;
                     }
-                    if (args.signingDetails.signatureSchemeVersion != SIGNING_BLOCK_V4) {
+                    if (args.signingDetails.getSignatureSchemeVersion() != SIGNING_BLOCK_V4) {
                         continue;
                     }
                     // For incremental installs, we bypass the verifier prior to install. Now
@@ -19817,11 +19819,11 @@
             // older certificate with which the current is ok with sharing permissions
             if (sourceSigningDetails.checkCapability(
                     parsedPackage.getSigningDetails(),
-                    PackageParser.SigningDetails.CertCapabilities.PERMISSION)) {
+                    SigningDetails.CertCapabilities.PERMISSION)) {
                 return true;
             } else if (parsedPackage.getSigningDetails().checkCapability(
                     sourceSigningDetails,
-                    PackageParser.SigningDetails.CertCapabilities.PERMISSION)) {
+                    SigningDetails.CertCapabilities.PERMISSION)) {
                 // the scanned package checks out, has signing certificate rotation
                 // history, and is newer; bring it over
                 synchronized (mLock) {
@@ -19936,7 +19938,7 @@
 
         try {
             // either use what we've been given or parse directly from the APK
-            if (args.signingDetails != PackageParser.SigningDetails.UNKNOWN) {
+            if (args.signingDetails != SigningDetails.UNKNOWN) {
                 parsedPackage.setSigningDetails(args.signingDetails);
             } else {
                 parsedPackage.setSigningDetails(ParsingPackageUtils.getSigningDetails(
@@ -19946,7 +19948,7 @@
             throw new PrepareFailure("Failed collect during installPackageLI", e);
         }
 
-        if (instantApp && parsedPackage.getSigningDetails().signatureSchemeVersion
+        if (instantApp && parsedPackage.getSigningDetails().getSignatureSchemeVersion()
                 < SignatureSchemeVersion.SIGNING_BLOCK_V2) {
             Slog.w(TAG, "Instant app package " + parsedPackage.getPackageName()
                     + " is not signed with at least APK Signature Scheme v2");
@@ -23023,10 +23025,10 @@
         final AndroidPackage androidPkg = mPackages.get(predefinedPkgName);
         if (androidPkg != null) {
             final SigningDetails signingDetail = androidPkg.getSigningDetails();
-            if (signingDetail != null && signingDetail.signatures != null) {
+            if (signingDetail != null && signingDetail.getSignatures() != null) {
                 try {
                     final MessageDigest msgDigest = MessageDigest.getInstance("SHA-256");
-                    for (Signature signature : signingDetail.signatures) {
+                    for (Signature signature : signingDetail.getSignatures()) {
                         if (TextUtils.equals(predefinedSignature,
                                 HexEncoding.encodeToString(msgDigest.digest(
                                         signature.toByteArray()), false))) {
@@ -25951,6 +25953,11 @@
 
     private int verifyReplacingVersionCode(PackageInfoLite pkgLite,
             long requiredInstalledVersionCode, int installFlags) {
+        if ((installFlags & PackageManager.INSTALL_APEX) != 0) {
+            return verifyReplacingVersionCodeForApex(
+                    pkgLite, requiredInstalledVersionCode, installFlags);
+        }
+
         String packageName = pkgLite.packageName;
         synchronized (mLock) {
             // Package which currently owns the data that the new package will own if installed.
@@ -25997,6 +26004,40 @@
         return PackageManager.INSTALL_SUCCEEDED;
     }
 
+    private int verifyReplacingVersionCodeForApex(PackageInfoLite pkgLite,
+            long requiredInstalledVersionCode, int installFlags) {
+        String packageName = pkgLite.packageName;
+
+        final PackageInfo activePackage = mApexManager.getPackageInfo(packageName,
+                ApexManager.MATCH_ACTIVE_PACKAGE);
+        if (activePackage == null) {
+            Slog.w(TAG, "Attempting to install new APEX package " + packageName);
+            return PackageManager.INSTALL_FAILED_WRONG_INSTALLED_VERSION;
+        }
+
+        final long activeVersion = activePackage.getLongVersionCode();
+        if (requiredInstalledVersionCode != PackageManager.VERSION_CODE_HIGHEST
+                && activeVersion != requiredInstalledVersionCode) {
+            Slog.w(TAG, "Installed version of APEX package " + packageName
+                    + " does not match required. Active version: " + activeVersion
+                    + " required: " + requiredInstalledVersionCode);
+            return PackageManager.INSTALL_FAILED_WRONG_INSTALLED_VERSION;
+        }
+
+        final boolean isAppDebuggable = (activePackage.applicationInfo.flags
+                & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
+        final long newVersionCode = pkgLite.getLongVersionCode();
+        if (!PackageManagerServiceUtils.isDowngradePermitted(installFlags, isAppDebuggable)
+                && newVersionCode < activeVersion) {
+            Slog.w(TAG, "Downgrade of APEX package " + packageName
+                    + " is not allowed. Active version: " + activeVersion
+                    + " attempted: " + newVersionCode);
+            return PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE;
+        }
+
+        return PackageManager.INSTALL_SUCCEEDED;
+    }
+
     /**
      * Check and throw if the given before/after packages would be considered a
      * downgrade.
@@ -26310,7 +26351,7 @@
             }
             return pkg.getSigningDetails().hasAncestorOrSelf(mPlatformPackage.getSigningDetails())
                     || mPlatformPackage.getSigningDetails().checkCapability(pkg.getSigningDetails(),
-                    PackageParser.SigningDetails.CertCapabilities.PERMISSION);
+                    SigningDetails.CertCapabilities.PERMISSION);
         }
 
         @Override
@@ -28049,6 +28090,56 @@
             }
         }
     }
+
+    @Override
+    public IntentSender getLaunchIntentSenderForPackage(String packageName, String callingPackage,
+            String featureId, int userId) throws RemoteException {
+        Objects.requireNonNull(packageName);
+        final int callingUid = Binder.getCallingUid();
+        enforceCrossUserPermission(callingUid, userId, false /* requireFullPermission */,
+                false /* checkShell */, "get launch intent sender for package");
+        final int packageUid = getPackageUid(callingPackage, 0 /* flags */, userId);
+        if (!UserHandle.isSameApp(callingUid, packageUid)) {
+            throw new SecurityException("getLaunchIntentSenderForPackage() from calling uid: "
+                    + callingUid + " does not own package: " + callingPackage);
+        }
+
+        // Using the same implementation with the #getLaunchIntentForPackage to get the ResolveInfo.
+        // Pass the resolveForStart as true in queryIntentActivities to skip the app filtering.
+        final Intent intentToResolve = new Intent(Intent.ACTION_MAIN);
+        intentToResolve.addCategory(Intent.CATEGORY_INFO);
+        intentToResolve.setPackage(packageName);
+        String resolvedType = intentToResolve.resolveTypeIfNeeded(mContext.getContentResolver());
+        List<ResolveInfo> ris = queryIntentActivitiesInternal(intentToResolve, resolvedType,
+                0 /* flags */, 0 /* privateResolveFlags */, callingUid, userId,
+                true /* resolveForStart */, false /* allowDynamicSplits */);
+        if (ris == null || ris.size() <= 0) {
+            intentToResolve.removeCategory(Intent.CATEGORY_INFO);
+            intentToResolve.addCategory(Intent.CATEGORY_LAUNCHER);
+            intentToResolve.setPackage(packageName);
+            resolvedType = intentToResolve.resolveTypeIfNeeded(mContext.getContentResolver());
+            ris = queryIntentActivitiesInternal(intentToResolve, resolvedType,
+                    0 /* flags */, 0 /* privateResolveFlags */, callingUid, userId,
+                    true /* resolveForStart */, false /* allowDynamicSplits */);
+        }
+
+        final Intent intent = new Intent(intentToResolve);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        // For the case of empty result, no component name is assigned into the intent. A
+        // non-launchable IntentSender which contains the failed intent is created. The
+        // SendIntentException is thrown if the IntentSender#sendIntent is invoked.
+        if (ris != null && !ris.isEmpty()) {
+            intent.setClassName(ris.get(0).activityInfo.packageName,
+                    ris.get(0).activityInfo.name);
+        }
+        final IIntentSender target = ActivityManager.getService().getIntentSenderWithFeature(
+                ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage,
+                featureId, null /* token */, null /* resultWho */,
+                1 /* requestCode */, new Intent[] { intent },
+                resolvedType != null ? new String[] { resolvedType } : null,
+                PendingIntent.FLAG_IMMUTABLE, null /* bOptions */, userId);
+        return new IntentSender(target);
+    }
 }
 
 interface PackageSender {
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index 8015063..f6d58e8 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -34,9 +34,9 @@
 import android.content.Intent;
 import android.content.pm.PackageInfoLite;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageParser;
 import android.content.pm.ResolveInfo;
 import android.content.pm.Signature;
+import android.content.pm.SigningDetails;
 import android.content.pm.parsing.ApkLiteParseUtils;
 import android.content.pm.parsing.PackageLite;
 import android.content.pm.parsing.result.ParseResult;
@@ -499,9 +499,9 @@
      */
     public static boolean comparePackageSignatures(PackageSetting pkgSetting,
             Signature[] signatures) {
-        return pkgSetting.signatures.mSigningDetails
-                == PackageParser.SigningDetails.UNKNOWN
-                || compareSignatures(pkgSetting.signatures.mSigningDetails.signatures, signatures)
+        final SigningDetails signingDetails = pkgSetting.signatures.mSigningDetails;
+        return signingDetails == SigningDetails.UNKNOWN
+                || compareSignatures(signingDetails.getSignatures(), signatures)
                 == PackageManager.SIGNATURE_MATCH;
     }
 
@@ -512,13 +512,13 @@
      * system upgrade) and {@code scannedSigs} will be in the newer format.
      */
     private static boolean matchSignaturesCompat(String packageName,
-            PackageSignatures packageSignatures, PackageParser.SigningDetails parsedSignatures) {
+            PackageSignatures packageSignatures, SigningDetails parsedSignatures) {
         ArraySet<Signature> existingSet = new ArraySet<Signature>();
-        for (Signature sig : packageSignatures.mSigningDetails.signatures) {
+        for (Signature sig : packageSignatures.mSigningDetails.getSignatures()) {
             existingSet.add(sig);
         }
         ArraySet<Signature> scannedCompatSet = new ArraySet<Signature>();
-        for (Signature sig : parsedSignatures.signatures) {
+        for (Signature sig : parsedSignatures.getSignatures()) {
             try {
                 Signature[] chainSignatures = sig.getChainSignatures();
                 for (Signature chainSig : chainSignatures) {
@@ -547,9 +547,9 @@
 
     private static boolean matchSignaturesRecover(
             String packageName,
-            PackageParser.SigningDetails existingSignatures,
-            PackageParser.SigningDetails parsedSignatures,
-            @PackageParser.SigningDetails.CertCapabilities int flags) {
+            SigningDetails existingSignatures,
+            SigningDetails parsedSignatures,
+            @SigningDetails.CertCapabilities int flags) {
         String msg = null;
         try {
             if (parsedSignatures.checkCapabilityRecover(existingSignatures, flags)) {
@@ -576,10 +576,10 @@
             PackageSetting disabledPkgSetting) {
         if (pkgSetting.signatures.mSigningDetails.checkCapability(
                 disabledPkgSetting.signatures.mSigningDetails,
-                PackageParser.SigningDetails.CertCapabilities.INSTALLED_DATA)
+                SigningDetails.CertCapabilities.INSTALLED_DATA)
                 || disabledPkgSetting.signatures.mSigningDetails.checkCapability(
                 pkgSetting.signatures.mSigningDetails,
-                PackageParser.SigningDetails.CertCapabilities.ROLLBACK)) {
+                SigningDetails.CertCapabilities.ROLLBACK)) {
             return true;
         } else {
             logCriticalInfo(Log.ERROR, "Updated system app mismatches cert on /system: " +
@@ -623,19 +623,19 @@
      * @throws PackageManagerException if the signatures did not match.
      */
     public static boolean verifySignatures(PackageSetting pkgSetting,
-            PackageSetting disabledPkgSetting, PackageParser.SigningDetails parsedSignatures,
+            PackageSetting disabledPkgSetting, SigningDetails parsedSignatures,
             boolean compareCompat, boolean compareRecover, boolean isRollback)
             throws PackageManagerException {
         final String packageName = pkgSetting.name;
         boolean compatMatch = false;
-        if (pkgSetting.signatures.mSigningDetails.signatures != null) {
+        if (pkgSetting.signatures.mSigningDetails.getSignatures() != null) {
             // Already existing package. Make sure signatures match
             boolean match = parsedSignatures.checkCapability(
                     pkgSetting.signatures.mSigningDetails,
-                    PackageParser.SigningDetails.CertCapabilities.INSTALLED_DATA)
+                    SigningDetails.CertCapabilities.INSTALLED_DATA)
                             || pkgSetting.signatures.mSigningDetails.checkCapability(
                                     parsedSignatures,
-                                    PackageParser.SigningDetails.CertCapabilities.ROLLBACK);
+                                    SigningDetails.CertCapabilities.ROLLBACK);
             if (!match && compareCompat) {
                 match = matchSignaturesCompat(packageName, pkgSetting.signatures,
                         parsedSignatures);
@@ -646,12 +646,12 @@
                         packageName,
                         pkgSetting.signatures.mSigningDetails,
                         parsedSignatures,
-                        PackageParser.SigningDetails.CertCapabilities.INSTALLED_DATA)
+                        SigningDetails.CertCapabilities.INSTALLED_DATA)
                                 || matchSignaturesRecover(
                                         packageName,
                                         parsedSignatures,
                                         pkgSetting.signatures.mSigningDetails,
-                                        PackageParser.SigningDetails.CertCapabilities.ROLLBACK);
+                                        SigningDetails.CertCapabilities.ROLLBACK);
             }
 
             if (!match && isApkVerificationForced(disabledPkgSetting)) {
@@ -674,7 +674,7 @@
         // Check for shared user signatures
         if (pkgSetting.getSharedUser() != null
                 && pkgSetting.getSharedUser().signatures.mSigningDetails
-                        != PackageParser.SigningDetails.UNKNOWN) {
+                        != SigningDetails.UNKNOWN) {
 
             // Already existing package. Make sure signatures match.  In case of signing certificate
             // rotation, the packages with newer certs need to be ok with being sharedUserId with
@@ -684,10 +684,10 @@
             boolean match =
                     parsedSignatures.checkCapability(
                             pkgSetting.getSharedUser().signatures.mSigningDetails,
-                            PackageParser.SigningDetails.CertCapabilities.SHARED_USER_ID)
+                            SigningDetails.CertCapabilities.SHARED_USER_ID)
                     || pkgSetting.getSharedUser().signatures.mSigningDetails.checkCapability(
                             parsedSignatures,
-                            PackageParser.SigningDetails.CertCapabilities.SHARED_USER_ID);
+                            SigningDetails.CertCapabilities.SHARED_USER_ID);
             // Special case: if the sharedUserId capability check failed it could be due to this
             // being the only package in the sharedUserId so far and the lineage being updated to
             // deny the sharedUserId capability of the previous key in the lineage.
@@ -704,11 +704,11 @@
                         matchSignaturesRecover(packageName,
                                 pkgSetting.getSharedUser().signatures.mSigningDetails,
                                 parsedSignatures,
-                                PackageParser.SigningDetails.CertCapabilities.SHARED_USER_ID)
+                                SigningDetails.CertCapabilities.SHARED_USER_ID)
                         || matchSignaturesRecover(packageName,
                                 parsedSignatures,
                                 pkgSetting.getSharedUser().signatures.mSigningDetails,
-                                PackageParser.SigningDetails.CertCapabilities.SHARED_USER_ID);
+                                SigningDetails.CertCapabilities.SHARED_USER_ID);
                 compatMatch |= match;
             }
             if (!match) {
@@ -729,13 +729,13 @@
                     if (packageName.equals(shUidPkgSetting.name)) {
                         continue;
                     }
-                    PackageParser.SigningDetails shUidSigningDetails =
+                    SigningDetails shUidSigningDetails =
                             shUidPkgSetting.getSigningDetails();
                     // The capability check only needs to be performed against the package if it is
                     // signed with a key that is in the lineage of the package being installed.
                     if (parsedSignatures.hasAncestor(shUidSigningDetails)) {
                         if (!parsedSignatures.checkCapability(shUidSigningDetails,
-                                PackageParser.SigningDetails.CertCapabilities.SHARED_USER_ID)) {
+                                SigningDetails.CertCapabilities.SHARED_USER_ID)) {
                             throw new PackageManagerException(
                                     INSTALL_FAILED_SHARED_USER_INCOMPATIBLE,
                                     "Package " + packageName
diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java
index 38e100e..e4bf090 100644
--- a/services/core/java/com/android/server/pm/PackageSettingBase.java
+++ b/services/core/java/com/android/server/pm/PackageSettingBase.java
@@ -27,9 +27,9 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IncrementalStatesInfo;
 import android.content.pm.PackageManager.UninstallReason;
-import android.content.pm.PackageParser;
 import android.content.pm.PackageUserState;
 import android.content.pm.Signature;
+import android.content.pm.SigningDetails;
 import android.content.pm.SuspendDialogInfo;
 import android.content.pm.overlay.OverlayPaths;
 import android.os.PersistableBundle;
@@ -217,10 +217,10 @@
     }
 
     public Signature[] getSignatures() {
-        return signatures.mSigningDetails.signatures;
+        return signatures.mSigningDetails.getSignatures();
     }
 
-    public PackageParser.SigningDetails getSigningDetails() {
+    public SigningDetails getSigningDetails() {
         return signatures.mSigningDetails;
     }
 
diff --git a/services/core/java/com/android/server/pm/PackageSignatures.java b/services/core/java/com/android/server/pm/PackageSignatures.java
index 394cdee..a1e5051 100644
--- a/services/core/java/com/android/server/pm/PackageSignatures.java
+++ b/services/core/java/com/android/server/pm/PackageSignatures.java
@@ -17,9 +17,9 @@
 package com.android.server.pm;
 
 import android.annotation.NonNull;
-import android.content.pm.PackageParser;
-import android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion;
 import android.content.pm.Signature;
+import android.content.pm.SigningDetails;
+import android.content.pm.SigningDetails.SignatureSchemeVersion;
 import android.util.Log;
 import android.util.TypedXmlPullParser;
 import android.util.TypedXmlSerializer;
@@ -35,40 +35,41 @@
 
 class PackageSignatures {
 
-    @NonNull PackageParser.SigningDetails mSigningDetails;
+    @NonNull SigningDetails mSigningDetails;
 
     PackageSignatures(PackageSignatures orig) {
-        if (orig != null && orig.mSigningDetails != PackageParser.SigningDetails.UNKNOWN) {
-            mSigningDetails = new PackageParser.SigningDetails(orig.mSigningDetails);
+        if (orig != null && orig.mSigningDetails != SigningDetails.UNKNOWN) {
+            mSigningDetails = new SigningDetails(orig.mSigningDetails);
         } else {
-            mSigningDetails = PackageParser.SigningDetails.UNKNOWN;
+            mSigningDetails = SigningDetails.UNKNOWN;
         }
     }
 
-    PackageSignatures(PackageParser.SigningDetails signingDetails) {
+    PackageSignatures(SigningDetails signingDetails) {
         mSigningDetails = signingDetails;
     }
 
     PackageSignatures() {
-        mSigningDetails = PackageParser.SigningDetails.UNKNOWN;
+        mSigningDetails = SigningDetails.UNKNOWN;
     }
 
     void writeXml(TypedXmlSerializer serializer, String tagName,
             ArrayList<Signature> writtenSignatures) throws IOException {
-        if (mSigningDetails.signatures == null) {
+        if (mSigningDetails.getSignatures() == null) {
             return;
         }
         serializer.startTag(null, tagName);
-        serializer.attributeInt(null, "count", mSigningDetails.signatures.length);
-        serializer.attributeInt(null, "schemeVersion", mSigningDetails.signatureSchemeVersion);
-        writeCertsListXml(serializer, writtenSignatures, mSigningDetails.signatures, false);
+        serializer.attributeInt(null, "count", mSigningDetails.getSignatures().length);
+        serializer.attributeInt(null, "schemeVersion", mSigningDetails.getSignatureSchemeVersion());
+        writeCertsListXml(serializer, writtenSignatures, mSigningDetails.getSignatures(), false);
 
         // if we have past signer certificate information, write it out
-        if (mSigningDetails.pastSigningCertificates != null) {
+        if (mSigningDetails.getPastSigningCertificates() != null) {
             serializer.startTag(null, "pastSigs");
-            serializer.attributeInt(null, "count", mSigningDetails.pastSigningCertificates.length);
+            serializer.attributeInt(null, "count",
+                    mSigningDetails.getPastSigningCertificates().length);
             writeCertsListXml(serializer, writtenSignatures,
-                    mSigningDetails.pastSigningCertificates, true);
+                    mSigningDetails.getPastSigningCertificates(), true);
             serializer.endTag(null, "pastSigs");
         }
         serializer.endTag(null, tagName);
@@ -106,8 +107,7 @@
 
     void readXml(TypedXmlPullParser parser, ArrayList<Signature> readSignatures)
             throws IOException, XmlPullParserException {
-        PackageParser.SigningDetails.Builder builder =
-                new PackageParser.SigningDetails.Builder();
+        SigningDetails.Builder builder = new SigningDetails.Builder();
 
         final int count = parser.getAttributeInt(null, "count", -1);
         if (count == -1) {
@@ -142,13 +142,13 @@
             PackageManagerService.reportSettingsProblem(Log.WARN,
                     "Error in package manager settings: <sigs> "
                             + "unable to convert certificate(s) to public key(s).");
-            mSigningDetails = PackageParser.SigningDetails.UNKNOWN;
+            mSigningDetails = SigningDetails.UNKNOWN;
         }
     }
 
     private int readCertsListXml(TypedXmlPullParser parser, ArrayList<Signature> readSignatures,
             ArrayList<Signature> signatures, int count, boolean isPastSigs,
-            PackageParser.SigningDetails.Builder builder)
+            SigningDetails.Builder builder)
             throws IOException, XmlPullParserException {
         int pos = 0;
 
@@ -306,25 +306,25 @@
         buf.append("PackageSignatures{");
         buf.append(Integer.toHexString(System.identityHashCode(this)));
         buf.append(" version:");
-        buf.append(mSigningDetails.signatureSchemeVersion);
+        buf.append(mSigningDetails.getSignatureSchemeVersion());
         buf.append(", signatures:[");
-        if (mSigningDetails.signatures != null) {
-            for (int i = 0; i < mSigningDetails.signatures.length; i++) {
+        if (mSigningDetails.getSignatures() != null) {
+            for (int i = 0; i < mSigningDetails.getSignatures().length; i++) {
                 if (i > 0) buf.append(", ");
                 buf.append(Integer.toHexString(
-                        mSigningDetails.signatures[i].hashCode()));
+                        mSigningDetails.getSignatures()[i].hashCode()));
             }
         }
         buf.append("]");
         buf.append(", past signatures:[");
-        if (mSigningDetails.pastSigningCertificates != null) {
-            for (int i = 0; i < mSigningDetails.pastSigningCertificates.length; i++) {
+        if (mSigningDetails.getPastSigningCertificates() != null) {
+            for (int i = 0; i < mSigningDetails.getPastSigningCertificates().length; i++) {
                 if (i > 0) buf.append(", ");
                 buf.append(Integer.toHexString(
-                        mSigningDetails.pastSigningCertificates[i].hashCode()));
+                        mSigningDetails.getPastSigningCertificates()[i].hashCode()));
                 buf.append(" flags: ");
-                buf.append(
-                        Integer.toHexString(mSigningDetails.pastSigningCertificates[i].getFlags()));
+                buf.append(Integer.toHexString(
+                        mSigningDetails.getPastSigningCertificates()[i].getFlags()));
             }
         }
         buf.append("]}");
diff --git a/services/core/java/com/android/server/pm/SELinuxMMAC.java b/services/core/java/com/android/server/pm/SELinuxMMAC.java
index c5fbfba..bdecaa7 100644
--- a/services/core/java/com/android/server/pm/SELinuxMMAC.java
+++ b/services/core/java/com/android/server/pm/SELinuxMMAC.java
@@ -19,8 +19,8 @@
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledAfter;
 import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageParser.SigningDetails;
 import android.content.pm.Signature;
+import android.content.pm.SigningDetails;
 import android.os.Environment;
 import android.util.Slog;
 import android.util.Xml;
@@ -577,7 +577,7 @@
         // Check for exact signature matches across all certs.
         Signature[] certs = mCerts.toArray(new Signature[0]);
         if (pkg.getSigningDetails() != SigningDetails.UNKNOWN
-                && !Signature.areExactMatch(certs, pkg.getSigningDetails().signatures)) {
+                && !Signature.areExactMatch(certs, pkg.getSigningDetails().getSignatures())) {
 
             // certs aren't exact match, but the package may have rotated from the known system cert
             if (certs.length > 1 || !pkg.getSigningDetails().hasCertificate(certs[0])) {
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 4823c29..94be11e 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -1129,12 +1129,13 @@
     // by that time.
     void insertPackageSettingLPw(PackageSetting p, AndroidPackage pkg) {
         // Update signatures if needed.
-        if (p.signatures.mSigningDetails.signatures == null) {
+        if (p.signatures.mSigningDetails.getSignatures() == null) {
             p.signatures.mSigningDetails = pkg.getSigningDetails();
         }
         // If this app defines a shared user id initialize
         // the shared user signatures as well.
-        if (p.sharedUser != null && p.sharedUser.signatures.mSigningDetails.signatures == null) {
+        if (p.sharedUser != null
+                && p.sharedUser.signatures.mSigningDetails.getSignatures() == null) {
             p.sharedUser.signatures.mSigningDetails = pkg.getSigningDetails();
         }
         addPackageSettingLPw(p, p.sharedUser);
@@ -2910,6 +2911,17 @@
             mReadMessages.append("Error reading: " + e.toString());
             PackageManagerService.reportSettingsProblem(Log.ERROR, "Error reading settings: " + e);
             Slog.wtf(PackageManagerService.TAG, "Error reading package manager settings", e);
+        } finally {
+            if (!mVersion.containsKey(StorageManager.UUID_PRIVATE_INTERNAL)) {
+                Slog.wtf(PackageManagerService.TAG,
+                        "No internal VersionInfo found in settings, using current.");
+                findOrCreateVersion(StorageManager.UUID_PRIVATE_INTERNAL).forceCurrent();
+            }
+            if (!mVersion.containsKey(StorageManager.UUID_PRIMARY_PHYSICAL)) {
+                Slog.wtf(PackageManagerService.TAG,
+                        "No external VersionInfo found in settings, using current.");
+                findOrCreateVersion(StorageManager.UUID_PRIMARY_PHYSICAL).forceCurrent();
+            }
         }
 
         // If the build is setup to drop runtime permissions
@@ -4424,7 +4436,7 @@
             pw.print(prefix); pw.print("  versionName="); pw.println(pkg.getVersionName());
             pw.print(prefix); pw.print("  usesNonSdkApi="); pw.println(pkg.isUsesNonSdkApi());
             pw.print(prefix); pw.print("  splits="); dumpSplitNames(pw, pkg); pw.println();
-            final int apkSigningVersion = pkg.getSigningDetails().signatureSchemeVersion;
+            final int apkSigningVersion = pkg.getSigningDetails().getSignatureSchemeVersion();
             pw.print(prefix); pw.print("  apkSigningVersion="); pw.println(apkSigningVersion);
             pw.print(prefix); pw.print("  applicationInfo=");
             pw.println(pkg.toAppInfoToString());
diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java
index 8f87b19..d0ee84c 100644
--- a/services/core/java/com/android/server/pm/StagingManager.java
+++ b/services/core/java/com/android/server/pm/StagingManager.java
@@ -28,7 +28,6 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.IntentSender;
-import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageInstaller.SessionInfo;
@@ -36,8 +35,8 @@
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.PackageParser.PackageParserException;
-import android.content.pm.PackageParser.SigningDetails;
-import android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion;
+import android.content.pm.SigningDetails;
+import android.content.pm.SigningDetails.SignatureSchemeVersion;
 import android.content.pm.parsing.PackageInfoWithoutStateUtils;
 import android.content.rollback.RollbackInfo;
 import android.content.rollback.RollbackManager;
@@ -80,6 +79,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Predicate;
@@ -112,6 +112,8 @@
     @GuardedBy("mSuccessfulStagedSessionIds")
     private final List<Integer> mSuccessfulStagedSessionIds = new ArrayList<>();
 
+    private final CompletableFuture<Void> mBootCompleted = new CompletableFuture<>();
+
     interface StagedSession {
         boolean isMultiPackage();
         boolean isApexSession();
@@ -136,7 +138,6 @@
         boolean hasParentSessionId();
         long getCommittedMillis();
         void abandon();
-        boolean notifyStartPreRebootVerification();
         void notifyEndPreRebootVerification();
         void verifySession();
     }
@@ -229,7 +230,7 @@
         final SigningDetails existingSigningDetails;
         try {
             existingSigningDetails = ApkSignatureVerifier.verify(
-                existingApexPkg.applicationInfo.sourceDir, SignatureSchemeVersion.JAR);
+                    existingApexPkg.applicationInfo.sourceDir, SignatureSchemeVersion.JAR);
         } catch (PackageParserException e) {
             throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
                     "Failed to parse APEX package " + existingApexPkg.applicationInfo.sourceDir
@@ -294,15 +295,6 @@
                 throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
                         "Failed to parse APEX package " + apexInfo.modulePath + " : " + e, e);
             }
-            final PackageInfo activePackage = mApexManager.getPackageInfo(packageInfo.packageName,
-                    ApexManager.MATCH_ACTIVE_PACKAGE);
-            if (activePackage == null) {
-                Slog.w(TAG, "Attempting to install new APEX package " + packageInfo.packageName);
-                throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
-                        "It is forbidden to install new APEX packages.");
-            }
-            checkRequiredVersionCode(session, activePackage);
-            checkDowngrade(session, activePackage, packageInfo);
             result.add(packageInfo);
             apexPackageNames.add(packageInfo.packageName);
         }
@@ -325,40 +317,6 @@
                 "Could not find rollback id for commit session: " + sessionId);
     }
 
-    private void checkRequiredVersionCode(final StagedSession session,
-            final PackageInfo activePackage) throws PackageManagerException {
-        if (session.sessionParams().requiredInstalledVersionCode
-                == PackageManager.VERSION_CODE_HIGHEST) {
-            return;
-        }
-        final long activeVersion = activePackage.applicationInfo.longVersionCode;
-        if (activeVersion != session.sessionParams().requiredInstalledVersionCode) {
-            throw new PackageManagerException(
-                    SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
-                    "Installed version of APEX package " + activePackage.packageName
-                            + " does not match required. Active version: " + activeVersion
-                            + " required: " + session.sessionParams().requiredInstalledVersionCode);
-        }
-    }
-
-    private void checkDowngrade(final StagedSession session,
-            final PackageInfo activePackage, final PackageInfo newPackage)
-            throws PackageManagerException {
-        final long activeVersion = activePackage.applicationInfo.longVersionCode;
-        final long newVersionCode = newPackage.applicationInfo.longVersionCode;
-        final boolean isAppDebuggable = (activePackage.applicationInfo.flags
-                & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
-        final boolean allowsDowngrade = PackageManagerServiceUtils.isDowngradePermitted(
-                session.sessionParams().installFlags, isAppDebuggable);
-        if (activeVersion > newVersionCode && !allowsDowngrade) {
-            throw new PackageManagerException(
-                    SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
-                    "Downgrade of APEX package " + newPackage.packageName
-                            + " is not allowed. Active version: " + activeVersion
-                            + " attempted: " + newVersionCode);
-        }
-    }
-
     // Reverts apex sessions and user data (if checkpoint is supported). Also reboots the device.
     private void abortCheckpoint(String failureReason, boolean supportsCheckpoint,
             boolean needsCheckpoint) {
@@ -863,7 +821,8 @@
             } else if (!session.isSessionReady()) {
                 // The framework got restarted before the pre-reboot verification could complete,
                 // restart the verification.
-                mPreRebootVerificationHandler.startPreRebootVerification(session);
+                Slog.i(TAG, "Restart verification for session=" + session.sessionId());
+                mBootCompleted.thenRun(() -> session.verifySession());
                 StagedSession session2 = sessions.set(j - 1, session);
                 sessions.set(i, session2);
                 j--;
@@ -1043,7 +1002,7 @@
 
     @VisibleForTesting
     void onBootCompletedBroadcastReceived() {
-        mPreRebootVerificationHandler.readyToStart();
+        mBootCompleted.complete(null);
         BackgroundThread.getExecutor().execute(() -> logFailedApexSessionsIfNecessary());
     }
 
@@ -1084,24 +1043,7 @@
         return session;
     }
 
-    // TODO(b/136257624): Temporary API to let PMS communicate with StagingManager. When all
-    //  verification logic is extracted out of StagingManager into PMS, we can remove
-    //  this.
-    void notifyVerificationComplete(StagedSession session) {
-        mPreRebootVerificationHandler.onPreRebootVerificationComplete(session);
-    }
-
-    // TODO(b/136257624): Temporary API to let PMS communicate with StagingManager. When all
-    //  verification logic is extracted out of StagingManager into PMS, we can remove
-    //  this.
-    void notifyPreRebootVerification_Apk_Complete(@NonNull StagedSession session) {
-        mPreRebootVerificationHandler.notifyPreRebootVerification_Apk_Complete(session);
-    }
-
     private final class PreRebootVerificationHandler extends Handler {
-        // Hold sessions before handler gets ready to do the verification.
-        private List<StagedSession> mPendingSessions;
-        private boolean mIsReady;
 
         PreRebootVerificationHandler(Looper looper) {
             super(looper);
@@ -1115,7 +1057,6 @@
          * <p><ul>
          *     <li>MSG_PRE_REBOOT_VERIFICATION_START</li>
          *     <li>MSG_PRE_REBOOT_VERIFICATION_APEX</li>
-         *     <li>MSG_PRE_REBOOT_VERIFICATION_APK</li>
          *     <li>MSG_PRE_REBOOT_VERIFICATION_END</li>
          * </ul></p>
          *
@@ -1123,8 +1064,7 @@
          */
         private static final int MSG_PRE_REBOOT_VERIFICATION_START = 1;
         private static final int MSG_PRE_REBOOT_VERIFICATION_APEX = 2;
-        private static final int MSG_PRE_REBOOT_VERIFICATION_APK = 3;
-        private static final int MSG_PRE_REBOOT_VERIFICATION_END = 4;
+        private static final int MSG_PRE_REBOOT_VERIFICATION_END = 3;
 
         @Override
         public void handleMessage(Message msg) {
@@ -1144,9 +1084,6 @@
                     case MSG_PRE_REBOOT_VERIFICATION_APEX:
                         handlePreRebootVerification_Apex(session, rollbackId);
                         break;
-                    case MSG_PRE_REBOOT_VERIFICATION_APK:
-                        handlePreRebootVerification_Apk(session);
-                        break;
                     case MSG_PRE_REBOOT_VERIFICATION_END:
                         handlePreRebootVerification_End(session);
                         break;
@@ -1159,35 +1096,15 @@
             }
         }
 
-        // Notify the handler that system is ready, and reschedule the pre-reboot verifications.
-        private synchronized void readyToStart() {
-            mIsReady = true;
-            if (mPendingSessions != null) {
-                for (int i = 0; i < mPendingSessions.size(); i++) {
-                    StagedSession session = mPendingSessions.get(i);
-                    startPreRebootVerification(session);
-                }
-                mPendingSessions = null;
-            }
-        }
-
         // Method for starting the pre-reboot verification
         private synchronized void startPreRebootVerification(
                 @NonNull StagedSession session) {
-            if (!mIsReady) {
-                if (mPendingSessions == null) {
-                    mPendingSessions = new ArrayList<>();
-                }
-                mPendingSessions.add(session);
-                return;
-            }
-
-            if (session.notifyStartPreRebootVerification()) {
+            mBootCompleted.thenRun(() -> {
                 int sessionId = session.sessionId();
                 Slog.d(TAG, "Starting preRebootVerification for session " + sessionId);
                 obtainMessage(MSG_PRE_REBOOT_VERIFICATION_START, sessionId, -1, session)
                         .sendToTarget();
-            }
+            });
         }
 
         private void onPreRebootVerificationFailure(StagedSession session,
@@ -1216,12 +1133,6 @@
 
         private void notifyPreRebootVerification_Apex_Complete(
                 @NonNull StagedSession session) {
-            obtainMessage(MSG_PRE_REBOOT_VERIFICATION_APK, session.sessionId(), -1, session)
-                    .sendToTarget();
-        }
-
-        private void notifyPreRebootVerification_Apk_Complete(
-                @NonNull StagedSession session) {
             obtainMessage(MSG_PRE_REBOOT_VERIFICATION_END, session.sessionId(), -1, session)
                     .sendToTarget();
         }
@@ -1309,19 +1220,6 @@
         }
 
         /**
-         * Pre-reboot verification state for apk files. Session is sent to
-         * {@link PackageManagerService} for verification and it notifies back the result via
-         * {@link #notifyPreRebootVerification_Apk_Complete}
-         */
-        private void handlePreRebootVerification_Apk(@NonNull StagedSession session) {
-            if (!session.containsApkSession()) {
-                notifyPreRebootVerification_Apk_Complete(session);
-                return;
-            }
-            session.verifySession();
-        }
-
-        /**
          * Pre-reboot verification state for wrapping up:
          * <p><ul>
          *     <li>enables rollback if required</li>
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackage.java b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackage.java
index 471a4d3..0d2bcec 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackage.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackage.java
@@ -21,8 +21,8 @@
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
-import android.content.pm.PackageParser;
 import android.content.pm.PermissionGroupInfo;
+import android.content.pm.SigningDetails;
 import android.content.pm.parsing.ParsingPackageRead;
 import android.content.pm.parsing.ParsingPackageUtils;
 import android.content.pm.parsing.component.ParsedAttribution;
@@ -200,7 +200,7 @@
      * The signature data of all APKs in this package, which must be exactly the same across the
      * base and splits.
      */
-    PackageParser.SigningDetails getSigningDetails();
+    SigningDetails getSigningDetails();
 
     /**
      * TODO(b/135203078): Move split stuff to an inner data class
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
index b4c6e9d..a7fe789 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
@@ -22,7 +22,7 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
-import android.content.pm.PackageParser;
+import android.content.pm.SigningDetails;
 import android.content.pm.parsing.ParsingPackage;
 import android.content.pm.parsing.ParsingPackageImpl;
 import android.content.pm.parsing.component.ParsedActivity;
@@ -250,7 +250,7 @@
     }
 
     @Override
-    public PackageImpl setSigningDetails(@Nullable PackageParser.SigningDetails value) {
+    public PackageImpl setSigningDetails(@Nullable SigningDetails value) {
         super.setSigningDetails(value);
         return this;
     }
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/ParsedPackage.java b/services/core/java/com/android/server/pm/parsing/pkg/ParsedPackage.java
index 657f32c..8e4ee6a 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/ParsedPackage.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/ParsedPackage.java
@@ -17,7 +17,7 @@
 package com.android.server.pm.parsing.pkg;
 
 import android.annotation.Nullable;
-import android.content.pm.PackageParser;
+import android.content.pm.SigningDetails;
 
 /**
  * Methods used for mutation after direct package parsing, mostly done inside
@@ -59,7 +59,7 @@
 
     ParsedPackage setSecondaryCpuAbi(String secondaryCpuAbi);
 
-    ParsedPackage setSigningDetails(PackageParser.SigningDetails signingDetails);
+    ParsedPackage setSigningDetails(SigningDetails signingDetails);
 
     ParsedPackage setSplitCodePaths(String[] splitCodePaths);
 
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 2d1178a..db4fe49 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -82,6 +82,7 @@
 import android.content.pm.ParceledListSlice;
 import android.content.pm.PermissionGroupInfo;
 import android.content.pm.PermissionInfo;
+import android.content.pm.SigningDetails;
 import android.content.pm.parsing.component.ParsedPermission;
 import android.content.pm.parsing.component.ParsedPermissionGroup;
 import android.content.pm.permission.SplitPermissionInfoParcelable;
@@ -3434,15 +3435,15 @@
         //     - or it shares a common signing certificate in its lineage with the defining package,
         //       and the defining package still trusts the old certificate for permissions
         //     - or it shares the above relationships with the system package
-        final PackageParser.SigningDetails sourceSigningDetails =
+        final SigningDetails sourceSigningDetails =
                 getSourcePackageSigningDetails(bp);
         return sourceSigningDetails.hasCommonSignerWithCapability(
                         pkg.getSigningDetails(),
-                        PackageParser.SigningDetails.CertCapabilities.PERMISSION)
+                        SigningDetails.CertCapabilities.PERMISSION)
                 || pkg.getSigningDetails().hasAncestorOrSelf(systemPackage.getSigningDetails())
                 || systemPackage.getSigningDetails().checkCapability(
                         pkg.getSigningDetails(),
-                        PackageParser.SigningDetails.CertCapabilities.PERMISSION);
+                        SigningDetails.CertCapabilities.PERMISSION);
     }
 
     private boolean shouldGrantPermissionByProtectionFlags(@NonNull AndroidPackage pkg,
@@ -3600,11 +3601,11 @@
     }
 
     @NonNull
-    private PackageParser.SigningDetails getSourcePackageSigningDetails(
+    private SigningDetails getSourcePackageSigningDetails(
             @NonNull Permission bp) {
         final PackageSetting ps = getSourcePackageSetting(bp);
         if (ps == null) {
-            return PackageParser.SigningDetails.UNKNOWN;
+            return SigningDetails.UNKNOWN;
         }
         return ps.getSigningDetails();
     }
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationDebug.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationDebug.java
index 39ed488..53d5ad4 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationDebug.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationDebug.java
@@ -117,10 +117,10 @@
 
         if (!reusedMap.isEmpty()) {
             if (!wasHeaderPrinted) {
-                Signature[] signatures = pkg.getSigningDetails().signatures;
+                Signature[] signatures = pkg.getSigningDetails().getSignatures();
                 String signaturesDigest = signatures == null ? null : Arrays.toString(
                         PackageUtils.computeSignaturesSha256Digests(
-                                pkg.getSigningDetails().signatures));
+                                pkg.getSigningDetails().getSignatures()));
 
                 writer.println(pkgState.getPackageName() + ":");
                 writer.increaseIndent();
diff --git a/services/core/java/com/android/server/policy/role/RoleServicePlatformHelperImpl.java b/services/core/java/com/android/server/policy/role/RoleServicePlatformHelperImpl.java
index c2596c7..c08faf8 100644
--- a/services/core/java/com/android/server/policy/role/RoleServicePlatformHelperImpl.java
+++ b/services/core/java/com/android/server/policy/role/RoleServicePlatformHelperImpl.java
@@ -323,7 +323,7 @@
                     dataOutputStream.writeUTF(disabledComponents.valueAt(i));
                 }
 
-                for (final Signature signature : pkg.getSigningDetails().signatures) {
+                for (final Signature signature : pkg.getSigningDetails().getSignatures()) {
                     dataOutputStream.write(signature.toByteArray());
                 }
             } catch (IOException e) {
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java b/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java
index 6366280..e006b65 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java
@@ -21,24 +21,23 @@
 import android.hardware.audio.common.V2_0.Uuid;
 import android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback;
 import android.hardware.soundtrigger.V2_3.ISoundTriggerHw;
-import android.hardware.soundtrigger.V2_3.Properties;
+import android.media.soundtrigger.AudioCapabilities;
+import android.media.soundtrigger.ConfidenceLevel;
+import android.media.soundtrigger.ModelParameter;
+import android.media.soundtrigger.ModelParameterRange;
+import android.media.soundtrigger.Phrase;
+import android.media.soundtrigger.PhraseRecognitionEvent;
+import android.media.soundtrigger.PhraseRecognitionExtra;
+import android.media.soundtrigger.PhraseSoundModel;
+import android.media.soundtrigger.Properties;
+import android.media.soundtrigger.RecognitionConfig;
+import android.media.soundtrigger.RecognitionEvent;
+import android.media.soundtrigger.RecognitionMode;
+import android.media.soundtrigger.RecognitionStatus;
+import android.media.soundtrigger.SoundModel;
+import android.media.soundtrigger.SoundModelType;
 import android.media.audio.common.AudioConfig;
 import android.media.audio.common.AudioOffloadInfo;
-import android.media.soundtrigger_middleware.AudioCapabilities;
-import android.media.soundtrigger_middleware.ConfidenceLevel;
-import android.media.soundtrigger_middleware.ModelParameter;
-import android.media.soundtrigger_middleware.ModelParameterRange;
-import android.media.soundtrigger_middleware.Phrase;
-import android.media.soundtrigger_middleware.PhraseRecognitionEvent;
-import android.media.soundtrigger_middleware.PhraseRecognitionExtra;
-import android.media.soundtrigger_middleware.PhraseSoundModel;
-import android.media.soundtrigger_middleware.RecognitionConfig;
-import android.media.soundtrigger_middleware.RecognitionEvent;
-import android.media.soundtrigger_middleware.RecognitionMode;
-import android.media.soundtrigger_middleware.RecognitionStatus;
-import android.media.soundtrigger_middleware.SoundModel;
-import android.media.soundtrigger_middleware.SoundModelType;
-import android.media.soundtrigger_middleware.SoundTriggerModuleProperties;
 import android.os.HidlMemory;
 import android.os.HidlMemoryUtil;
 import android.os.ParcelFileDescriptor;
@@ -55,9 +54,9 @@
  */
 class ConversionUtil {
     static @NonNull
-    SoundTriggerModuleProperties hidl2aidlProperties(
+    Properties hidl2aidlProperties(
             @NonNull ISoundTriggerHw.Properties hidlProperties) {
-        SoundTriggerModuleProperties aidlProperties = new SoundTriggerModuleProperties();
+        Properties aidlProperties = new Properties();
         aidlProperties.implementor = hidlProperties.implementor;
         aidlProperties.description = hidlProperties.description;
         aidlProperties.version = hidlProperties.version;
@@ -75,9 +74,9 @@
         return aidlProperties;
     }
 
-    static @NonNull SoundTriggerModuleProperties hidl2aidlProperties(
-            @NonNull Properties hidlProperties) {
-        SoundTriggerModuleProperties aidlProperties = hidl2aidlProperties(hidlProperties.base);
+    static @NonNull Properties hidl2aidlProperties(
+            @NonNull android.hardware.soundtrigger.V2_3.Properties hidlProperties) {
+        Properties aidlProperties = hidl2aidlProperties(hidlProperties.base);
         aidlProperties.supportedModelArch = hidlProperties.supportedModelArch;
         aidlProperties.audioCapabilities =
                 hidl2aidlAudioCapabilities(hidlProperties.audioCapabilities);
@@ -216,9 +215,11 @@
     }
 
     static @NonNull android.hardware.soundtrigger.V2_3.RecognitionConfig aidl2hidlRecognitionConfig(
-            @NonNull RecognitionConfig aidlConfig) {
+            @NonNull RecognitionConfig aidlConfig, int deviceHandle, int ioHandle) {
         android.hardware.soundtrigger.V2_3.RecognitionConfig hidlConfig =
                 new android.hardware.soundtrigger.V2_3.RecognitionConfig();
+        hidlConfig.base.header.captureDevice = deviceHandle;
+        hidlConfig.base.header.captureHandle = ioHandle;
         hidlConfig.base.header.captureRequested = aidlConfig.captureRequested;
         for (PhraseRecognitionExtra aidlPhraseExtra : aidlConfig.phraseRecognitionExtras) {
             hidlConfig.base.header.phrases.add(aidl2hidlPhraseRecognitionExtra(aidlPhraseExtra));
@@ -299,8 +300,6 @@
         aidlEvent.status = hidl2aidlRecognitionStatus(hidlEvent.status);
         aidlEvent.type = hidl2aidlSoundModelType(hidlEvent.type);
         aidlEvent.captureAvailable = hidlEvent.captureAvailable;
-        // hidlEvent.captureSession is never a valid field.
-        aidlEvent.captureSession = -1;
         aidlEvent.captureDelayMs = hidlEvent.captureDelayMs;
         aidlEvent.capturePreambleMs = hidlEvent.capturePreambleMs;
         aidlEvent.triggerInData = hidlEvent.triggerInData;
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/DefaultHalFactory.java b/services/core/java/com/android/server/soundtrigger_middleware/DefaultHalFactory.java
new file mode 100644
index 0000000..2f2cb59
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/DefaultHalFactory.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.soundtrigger_middleware;
+
+import android.annotation.NonNull;
+import android.hardware.soundtrigger.V2_0.ISoundTriggerHw;
+import android.os.HwBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemProperties;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * This is the basic implementation of HalFactory, which uses either the default STHAL or a mock.
+ *
+ * The choice of which HAL to use is as follows:
+ * - Get the (int) value of "debug.soundtrigger_middleware.use_mock_hal" sysprop, if it doesn't
+ *   exist, assume 0.
+ * - If the value is 0, use the default HAL on the device. Connect to the latest-version "default"
+ *   instance declared in the device manifest (either AIDL or HIDL).
+ * - If the value is 2, connect to a "mock" instance of the latest v2.x (HIDL).
+ * - If the value is 3, connect to a "mock" instance of soundtrigger3 (AIDL).
+ * - Otherwise, throw.
+ */
+class DefaultHalFactory implements HalFactory {
+    private static final String TAG = "SoundTriggerMiddlewareDefaultHalFactory";
+
+    private static final @NonNull ICaptureStateNotifier mCaptureStateNotifier =
+            new ExternalCaptureStateTracker();
+
+    private static final int USE_DEFAULT_HAL = 0;
+    private static final int USE_MOCK_HAL_V2 = 2;
+    private static final int USE_MOCK_HAL_V3 = 3;
+
+    @Override
+    public ISoundTriggerHal create() {
+        try {
+            int mockHal = SystemProperties.getInt("debug.soundtrigger_middleware.use_mock_hal",
+                    USE_DEFAULT_HAL);
+            if (mockHal == USE_DEFAULT_HAL) {
+                // Use production HAL.
+
+                // Try soundtrigger3 (AIDL) first.
+                final String aidlServiceName =
+                        android.hardware.soundtrigger3.ISoundTriggerHw.class.getCanonicalName()
+                                + "/default";
+                if (ServiceManager.isDeclared(aidlServiceName)) {
+                    Log.i(TAG, "Connecting to default soundtrigger3.ISoundTriggerHw");
+                    return new SoundTriggerHw3Compat(ServiceManager.waitForService(aidlServiceName),
+                            () -> {
+                                // This property needs to be defined in an init.rc script and
+                                // trigger a HAL reboot.
+                                SystemProperties.set("sys.audio.restart.hal", "1");
+                            });
+                }
+
+                // Fallback to soundtrigger-V2.x (HIDL).
+                Log.i(TAG, "Connecting to default soundtrigger-V2.x.ISoundTriggerHw");
+                ISoundTriggerHw driver = ISoundTriggerHw.getService(true);
+                return SoundTriggerHw2Compat.create(driver, () -> {
+                    // This property needs to be defined in an init.rc script and
+                    // trigger a HAL reboot.
+                    SystemProperties.set("sys.audio.restart.hal", "1");
+                }, mCaptureStateNotifier);
+            } else if (mockHal == USE_MOCK_HAL_V2) {
+                // Use V2 mock.
+                Log.i(TAG, "Connecting to mock soundtrigger-V2.x.ISoundTriggerHw");
+                HwBinder.setTrebleTestingOverride(true);
+                try {
+                    ISoundTriggerHw driver = ISoundTriggerHw.getService("mock", true);
+                    return SoundTriggerHw2Compat.create(driver, () -> {
+                        try {
+                            driver.debug(null, new ArrayList<>(Arrays.asList("reboot")));
+                        } catch (Exception e) {
+                            Log.e(TAG, "Failed to reboot mock HAL", e);
+                        }
+                    }, mCaptureStateNotifier);
+                } finally {
+                    HwBinder.setTrebleTestingOverride(false);
+                }
+            } else if (mockHal == USE_MOCK_HAL_V3) {
+                // Use V3 mock.
+                final String aidlServiceName =
+                        android.hardware.soundtrigger3.ISoundTriggerHw.class.getCanonicalName()
+                                + "/mock";
+                Log.i(TAG, "Connecting to mock soundtrigger3.ISoundTriggerHw");
+                return new SoundTriggerHw3Compat(ServiceManager.waitForService(aidlServiceName),
+                        () -> {
+                            try {
+                                ServiceManager.waitForService(aidlServiceName).shellCommand(null,
+                                        null, null, new String[]{"reboot"}, null, null);
+                            } catch (Exception e) {
+                                Log.e(TAG, "Failed to reboot mock HAL", e);
+                            }
+                        });
+            } else {
+                throw new RuntimeException("Unknown HAL mock version: " + mockHal);
+            }
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/ExternalCaptureStateTracker.java b/services/core/java/com/android/server/soundtrigger_middleware/ExternalCaptureStateTracker.java
index 9404904..d195fbe 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/ExternalCaptureStateTracker.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/ExternalCaptureStateTracker.java
@@ -16,42 +16,55 @@
 
 package com.android.server.soundtrigger_middleware;
 
+import android.annotation.NonNull;
 import android.util.Log;
 
+import java.util.LinkedList;
+import java.util.List;
 import java.util.concurrent.Semaphore;
-import java.util.function.Consumer;
 
 /**
  * This is a never-give-up listener for sound trigger external capture state notifications, as
  * published by the audio policy service.
  *
  * This class will constantly try to connect to the service over a background thread and tolerate
- * its death. The client will be notified by a single provided function that is called in a
- * synchronized manner.
- * For simplicity, there is currently no way to stop the tracker. This is possible to add if the
- * need ever arises.
+ * its death.
  */
-class ExternalCaptureStateTracker {
+class ExternalCaptureStateTracker implements ICaptureStateNotifier {
     private static final String TAG = "CaptureStateTracker";
-    /** Our client's listener. */
-    private final Consumer<Boolean> mListener;
+
+    /** Our client's listeners. Also used as lock. */
+    private final List<Listener> mListeners = new LinkedList<>();
+
+    /** Conservatively, until notified otherwise, we assume capture is active. */
+    private boolean mCaptureActive = true;
+
     /** This semaphore will get a permit every time we need to reconnect. */
     private final Semaphore mNeedToConnect = new Semaphore(1);
 
     /**
      * Constructor. Will start a background thread to do the work.
-     *
-     * @param listener A client provided listener that will be called on state
-     *                 changes. May be
-     *                 called multiple consecutive times with the same value. Never
-     *                 called
-     *                 concurrently.
      */
-    ExternalCaptureStateTracker(Consumer<Boolean> listener) {
-        mListener = listener;
+    ExternalCaptureStateTracker() {
         new Thread(this::run).start();
     }
 
+
+    @Override
+    public boolean registerListener(@NonNull Listener listener) {
+        synchronized (mListeners) {
+            mListeners.add(listener);
+            return mCaptureActive;
+        }
+    }
+
+    @Override
+    public void unregisterListener(Listener listener) {
+        synchronized (mListeners) {
+            mListeners.remove(listener);
+        }
+    }
+
     /**
      * Routine for the background thread. Keeps trying to reconnect.
      */
@@ -74,7 +87,12 @@
      */
     private void setCaptureState(boolean active) {
         try {
-            mListener.accept(active);
+            synchronized (mListeners) {
+                mCaptureActive = active;
+                for (Listener listener : mListeners) {
+                    listener.onCaptureStateChange(active);
+                }
+            }
         } catch (Exception e) {
             Log.e(TAG, "Exception caught while setting capture state", e);
         }
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/HalFactory.java b/services/core/java/com/android/server/soundtrigger_middleware/HalFactory.java
index b19e2ed..6da8a79 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/HalFactory.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/HalFactory.java
@@ -16,16 +16,14 @@
 
 package com.android.server.soundtrigger_middleware;
 
-import android.hardware.soundtrigger.V2_0.ISoundTriggerHw;
-
 /**
- * A factory for creating instances of {@link ISoundTriggerHw}.
+ * A factory for creating instances of {@link ISoundTriggerHal}.
  *
  * @hide
  */
 public interface HalFactory {
     /**
-     * @return An instance of {@link ISoundTriggerHw}.
+     * @return An instance of {@link ISoundTriggerHal}.
      */
-    ISoundTriggerHw create();
+    ISoundTriggerHal create();
 }
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/ICaptureStateNotifier.java b/services/core/java/com/android/server/soundtrigger_middleware/ICaptureStateNotifier.java
new file mode 100644
index 0000000..07d83ca
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/ICaptureStateNotifier.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.soundtrigger_middleware;
+
+import android.annotation.NonNull;
+
+/**
+ * Allow registering listeners for tracking changes in audio capture state (when recording starts /
+ * stops). The client will be notified in a synchronized manner.
+ */
+interface ICaptureStateNotifier {
+    interface Listener {
+        void onCaptureStateChange(boolean state);
+    }
+
+    /**
+     * Register a listener for state change notifications. Returns the current capture state and
+     * any subsequent changes will be sent to the listener.
+     * @param listener The listener.
+     * @return The state at the time of registration.
+     */
+    boolean registerListener(@NonNull Listener listener);
+
+    /**
+     * Unregister a listener, previously registered with {@link #registerListener(Listener)}.
+     * Once this call returns, no more invocations of the listener will be made.
+     */
+    void unregisterListener(@NonNull Listener listener);
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerHal.java b/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerHal.java
new file mode 100644
index 0000000..aa85dd0
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerHal.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.soundtrigger_middleware;
+
+import android.hardware.soundtrigger3.ISoundTriggerHw;
+import android.hardware.soundtrigger3.ISoundTriggerHwCallback;
+import android.hardware.soundtrigger3.ISoundTriggerHwGlobalCallback;
+import android.media.soundtrigger.ModelParameterRange;
+import android.media.soundtrigger.PhraseRecognitionEvent;
+import android.media.soundtrigger.PhraseSoundModel;
+import android.media.soundtrigger.Properties;
+import android.media.soundtrigger.RecognitionConfig;
+import android.media.soundtrigger.RecognitionEvent;
+import android.media.soundtrigger.SoundModel;
+import android.os.IBinder;
+
+/**
+ * This interface mimics the soundtrigger HAL interface, with a few key differences:
+ * <ul>
+ * <li>Generally, methods should not throw, except for the following cases:
+ *   <ul>
+ *   <li>Any unexpected HAL behavior is considered an internal HAL malfunction and should be thrown
+ *   as a {@link HalException}, from any method.
+ *   <li>A {@link RuntimeException} with a {@link android.os.DeadObjectException} cause represents
+ *   a dead HAL process and may be thrown by any method.
+ *   <li>Implementations of earlier versions of the interface may throw a
+ *   {@link RecoverableException} with a
+ *   {@link android.media.soundtrigger.Status#OPERATION_NOT_SUPPORTED} for methods that
+ *   have been introduced in later versions of the interface.
+ *   <li>Certain methods are allowed to throw a {@link RecoverableException} with a
+ *   {@link android.media.soundtrigger.Status#RESOURCE_CONTENTION} to indicate transient
+ *   failures.
+ *   </ul>
+ * <li>Some binder-specific details are hidden.
+ * <li>No RemoteExceptions are specified. Some implementations of this interface may rethrow
+ * RemoteExceptions as RuntimeExceptions, some can guarantee handling them somehow and never throw
+ * them.
+ * </ul>
+ * For cases where the client wants to explicitly handle specific versions of the underlying driver
+ * interface, they may call {@link #interfaceDescriptor()}.
+ * <p>
+ * <b>Note to maintainers</b>: This class must always be kept in sync with the latest version,
+ * so that clients have access to the entire functionality without having to burden themselves with
+ * compatibility, as much as possible.
+ */
+interface ISoundTriggerHal {
+    /**
+     * @see ISoundTriggerHw#getProperties()
+     */
+    Properties getProperties();
+
+    /**
+     * @see ISoundTriggerHw#registerGlobalCallback(ISoundTriggerHwGlobalCallback)
+     */
+    void registerCallback(GlobalCallback callback);
+
+    /**
+     * @see ISoundTriggerHw#loadSoundModel(android.media.soundtrigger.SoundModel,
+     * ISoundTriggerHwCallback)
+     */
+    int loadSoundModel(SoundModel soundModel, ModelCallback callback);
+
+    /**
+     * @see ISoundTriggerHw#loadPhraseSoundModel(android.media.soundtrigger.PhraseSoundModel,
+     * ISoundTriggerHwCallback)
+     */
+    int loadPhraseSoundModel(PhraseSoundModel soundModel, ModelCallback callback);
+
+    /**
+     * @see ISoundTriggerHw#unloadSoundModel(int)
+     */
+    void unloadSoundModel(int modelHandle);
+
+    /**
+     * @see ISoundTriggerHw#startRecognition(int, int, int, RecognitionConfig)
+     */
+    void startRecognition(int modelHandle, int deviceHandle, int ioHandle,
+            RecognitionConfig config);
+
+    /**
+     * @see ISoundTriggerHw#stopRecognition(int)
+     */
+    void stopRecognition(int modelHandle);
+
+    /**
+     * @see ISoundTriggerHw#forceRecognitionEvent(int)
+     */
+    void forceRecognitionEvent(int modelHandle);
+
+    /**
+     * @return null if not supported.
+     * @see ISoundTriggerHw#queryParameter(int, int)
+     */
+    ModelParameterRange queryParameter(int modelHandle, int param);
+
+    /**
+     * @see ISoundTriggerHw#getParameter(int, int)
+     */
+    int getModelParameter(int modelHandle, int param);
+
+    /**
+     * @see ISoundTriggerHw#setParameter(int, int, int)
+     */
+    void setModelParameter(int modelHandle, int param, int value);
+
+    /**
+     * @see IBinder#getInterfaceDescriptor()
+     */
+    String interfaceDescriptor();
+
+    /**
+     * @see IBinder#linkToDeath(IBinder.DeathRecipient, int)
+     */
+    void linkToDeath(IBinder.DeathRecipient recipient);
+
+    /**
+     * @see IBinder#unlinkToDeath(IBinder.DeathRecipient, int)
+     */
+    void unlinkToDeath(IBinder.DeathRecipient recipient);
+
+    /*
+     * This is only useful for testing decorators and doesn't actually do anything with the real
+     * HAL. This method would block until all callbacks that were previously generated have been
+     * invoked. For most decorators, this merely flushes the delegate, but for delegates that may
+     * have additional buffers for callbacks this should flush them.
+     */
+    void flushCallbacks();
+
+    /**
+     * Kill and restart the HAL instance. This is typically a last resort for error recovery and may
+     * result in other related services being killed.
+     */
+    void reboot();
+
+    /**
+     * Called when this interface is guaranteed to no longer be used and can free up any resources
+     * used.
+     */
+    void detach();
+
+    /**
+     * Callback interface for model-related events.
+     */
+    interface ModelCallback {
+        /**
+         * @see ISoundTriggerHwCallback#recognitionCallback(int, RecognitionEvent)
+         */
+        void recognitionCallback(int modelHandle, RecognitionEvent event);
+
+        /**
+         * @see ISoundTriggerHwCallback#phraseRecognitionCallback(int, PhraseRecognitionEvent)
+         */
+        void phraseRecognitionCallback(int modelHandle, PhraseRecognitionEvent event);
+
+        /**
+         * @see ISoundTriggerHwCallback#modelUnloaded(int)
+         */
+        void modelUnloaded(int modelHandle);
+    }
+
+    /**
+     * Callback interface for global events.
+     */
+    interface GlobalCallback {
+        /**
+         * @see ISoundTriggerHwGlobalCallback#onResourcesAvailable()
+         */
+        void onResourcesAvailable();
+    }
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerHw2.java b/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerHw2.java
deleted file mode 100644
index 8b434bd..0000000
--- a/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerHw2.java
+++ /dev/null
@@ -1,158 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.soundtrigger_middleware;
-
-import android.hardware.soundtrigger.V2_3.ModelParameterRange;
-import android.hidl.base.V1_0.IBase;
-import android.os.IHwBinder;
-
-/**
- * This interface mimics android.hardware.soundtrigger.V2_x.ISoundTriggerHw and
- * android.hardware.soundtrigger.V2_x.ISoundTriggerHwCallback, with a few key differences:
- * <ul>
- * <li>Methods in the original interface generally have a status return value and potentially a
- * second return value which is the actual return value. This is reflected via a synchronous
- * callback, which is not very pleasant to work with. This interface replaces that pattern with
- * the convention that a HalException is thrown for non-OK status, and then we can use the
- * return value for the actual return value.
- * <li>This interface will always include all the methods from the latest 2.x version (and thus
- * from every 2.x version) interface, with the convention that unsupported methods throw a
- * {@link RecoverableException} with a
- * {@link android.media.soundtrigger_middleware.Status#OPERATION_NOT_SUPPORTED}
- * code.
- * <li>Cases where the original interface had multiple versions of a method representing the exact
- * thing, or there exists a trivial conversion between the new and old version, this interface
- * represents only the latest version, without any _version suffixes.
- * <li>Removes some of the obscure IBinder methods.
- * <li>No RemoteExceptions are specified. Some implementations of this interface may rethrow
- * RemoteExceptions as RuntimeExceptions, some can guarantee handling them somehow and never throw
- * them.
- * <li>soundModelCallback has been removed, since nobody cares about it. Implementations are free
- * to silently discard it.
- * </ul>
- * For cases where the client wants to explicitly handle specific versions of the underlying driver
- * interface, they may call {@link #interfaceDescriptor()}.
- * <p>
- * <b>Note to maintainers</b>: This class must always be kept in sync with the latest 2.x version,
- * so that clients have access to the entire functionality without having to burden themselves with
- * compatibility, as much as possible.
- */
-public interface ISoundTriggerHw2 {
-    /**
-     * @see android.hardware.soundtrigger.V2_3.ISoundTriggerHw#getPropertiesEx(
-     * android.hardware.soundtrigger.V2_3.ISoundTriggerHw.getPropertiesExCallback)
-     */
-    android.hardware.soundtrigger.V2_3.Properties getProperties();
-
-    /**
-     * @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#loadSoundModel_2_1(android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel,
-     * android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback, int,
-     * android.hardware.soundtrigger.V2_1.ISoundTriggerHw.loadSoundModel_2_1Callback)
-     */
-    int loadSoundModel(
-            android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel soundModel,
-            SoundTriggerHw2Compat.Callback callback, int cookie);
-
-    /**
-     * @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#loadPhraseSoundModel_2_1(android.hardware.soundtrigger.V2_1.ISoundTriggerHw.PhraseSoundModel,
-     * android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback, int,
-     * android.hardware.soundtrigger.V2_1.ISoundTriggerHw.loadPhraseSoundModel_2_1Callback)
-     */
-    int loadPhraseSoundModel(
-            android.hardware.soundtrigger.V2_1.ISoundTriggerHw.PhraseSoundModel soundModel,
-            SoundTriggerHw2Compat.Callback callback, int cookie);
-
-    /**
-     * @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#unloadSoundModel(int)
-     */
-    void unloadSoundModel(int modelHandle);
-
-    /**
-     * @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#stopRecognition(int)
-     */
-    void stopRecognition(int modelHandle);
-
-    /**
-     * @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#stopAllRecognitions()
-     */
-    void stopAllRecognitions();
-
-    /**
-     * @see android.hardware.soundtrigger.V2_3.ISoundTriggerHw#startRecognition_2_3(int,
-     * android.hardware.soundtrigger.V2_3.RecognitionConfig,
-     * android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback, int)
-     */
-    void startRecognition(int modelHandle,
-            android.hardware.soundtrigger.V2_3.RecognitionConfig config,
-            SoundTriggerHw2Compat.Callback callback, int cookie);
-
-    /**
-     * @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#getModelState(int)
-     */
-    void getModelState(int modelHandle);
-
-    /**
-     * @see android.hardware.soundtrigger.V2_3.ISoundTriggerHw#getParameter(int, int,
-     * ISoundTriggerHw.getParameterCallback)
-     */
-    int getModelParameter(int modelHandle, int param);
-
-    /**
-     * @see android.hardware.soundtrigger.V2_3.ISoundTriggerHw#setParameter(int, int, int)
-     */
-    void setModelParameter(int modelHandle, int param, int value);
-
-    /**
-     * @return null if not supported.
-     * @see android.hardware.soundtrigger.V2_3.ISoundTriggerHw#queryParameter(int, int,
-     * ISoundTriggerHw.queryParameterCallback)
-     */
-    ModelParameterRange queryParameter(int modelHandle, int param);
-
-    /**
-     * @see IHwBinder#linkToDeath(IHwBinder.DeathRecipient, long)
-     */
-    boolean linkToDeath(IHwBinder.DeathRecipient recipient, long cookie);
-
-    /**
-     * @see IHwBinder#unlinkToDeath(IHwBinder.DeathRecipient)
-     */
-    boolean unlinkToDeath(IHwBinder.DeathRecipient recipient);
-
-    /**
-     * @see IBase#interfaceDescriptor()
-     */
-    String interfaceDescriptor() throws android.os.RemoteException;
-
-    interface Callback {
-        /**
-         * @see android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback#recognitionCallback_2_1(android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.RecognitionEvent,
-         * int)
-         */
-        void recognitionCallback(
-                android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.RecognitionEvent event,
-                int cookie);
-
-        /**
-         * @see android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback#phraseRecognitionCallback_2_1(android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.PhraseRecognitionEvent,
-         * int)
-         */
-        void phraseRecognitionCallback(
-                android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.PhraseRecognitionEvent event,
-                int cookie);
-    }
-}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerMiddlewareInternal.java b/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerMiddlewareInternal.java
index a90053a..60f89da 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerMiddlewareInternal.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerMiddlewareInternal.java
@@ -16,18 +16,18 @@
 
 package com.android.server.soundtrigger_middleware;
 
-import android.media.ICaptureStateListener;
 import android.media.soundtrigger_middleware.ISoundTriggerCallback;
 import android.media.soundtrigger_middleware.ISoundTriggerModule;
 import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
 
 /**
- * This interface unifies methods from ISoundTriggerMiddlewareService and ICaptureStateListener.
+ * This interface closely follows ISoundTriggerMiddlewareService with some subtle changes for
+ * convenience.
  *
  * The ISoundTriggerMiddlewareService have been modified to exclude identity information and the
  * RemoteException signature, both of which are only relevant at the service boundary layer.
  */
-public interface ISoundTriggerMiddlewareInternal extends ICaptureStateListener {
+public interface ISoundTriggerMiddlewareInternal {
     /**
      * Query the available modules and their capabilities.
      */
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/README.md b/services/core/java/com/android/server/soundtrigger_middleware/README.md
index 416548d..016e5c9 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/README.md
+++ b/services/core/java/com/android/server/soundtrigger_middleware/README.md
@@ -1,19 +1,145 @@
 # Sound Trigger Middleware
-TODO: Add component description.
 
-## Notes about thread synchronization
+## Overview
+Sound Trigger Middleware is a system service that exposes sound trigger functionality (low-power
+detection of acoustic events) to applications and higher-level system service.
+
+It has the following roles:
+- Isolating the soundtrigger HAL from potentially untrusted clients.
+- Enforcing correct behavior of the clients.
+- Enforcing correct behavior of the HAL and attempting to recover from failures.
+- Enforcing permissions for using soundtrigger functionality.
+- Serializing access to the HAL.
+- Logging soundtrigger usage in a comprehensive and consistent manner.
+- Generating a dumpsys report including current state and history of operations.
+- Providing a standard interface regardless which version of the HAL is implemented and gracefully
+  degrading operation whenever necessary.
+
+## Structure
+
+The service implementation can be divided into three main layers:
+
+- The "bottom layer" is concerned with HAL compatibility - making all HAL versions look and behave
+  the same.
+- The "middle layer" is concerned with the business logic of the service.
+- The "top layer" is concerned with exposing this functionality as a System Service and integrating
+  with other parts of the system.
+
+### HAL Compatibility Layer
+
+This layer implements the `ISoundTriggerHal` interface, which is the version-agnostic representation
+of the sound trigger HAL driver. It has two main implementations, `SoundTriggerHw2Compat` and
+`SoundTriggerHw3Compat` responsible for adapting to V2.x and V3 HAL drivers, respectively, including
+supporting their respective minor-version differences.
+
+This layer also includes several `ISoundTriggerHal` decorators, such as `SoundTriggerHalWatchdog`
+that enforces deadlines on calls into the HAL, and `SoundTriggerHalEnforcer` which enforces that
+the HAL respects the expected protocol.
+
+The decorator-based design is an effective tool for separation of aspects and modularity, thus
+keeping classes relatively small and focused on one concern. It is also very effective for
+testability by following dependency injection principles.
+
+### Business Logic Layer
+
+This layer also uses a decorator-based design for separation of concerns. The main interface being
+decorated is `ISoundTriggerMiddlwareInternal`, which closely follows the external-facing AIDL
+interface, `ISoundTriggerMiddlewareService`.
+
+Each of the decorators serves a focused purpose: for example, `SoundTriggerMiddlwarePermission`
+deals with enforcing permissions required for the various methods, `SoundTriggerMiddlewareLogging`
+logs all API usage, `SoundTriggerMiddlewareValidation` enforces correct usage of the protocol and
+isolates client errors from internal server errors.
+
+At the bottom of this decorator stack is `SoundTriggerMiddlewareImpl` / `SoundTriggerModule`, which
+are the adapter between `ISoundTriggerHal` and `ISoundTriggerMiddlwareInternal`, introducing the
+notion of having separate client sessions sharing the same HAL.
+
+### Service Layer
+
+This layer ties everything together. It instantiates the actual system service and the decorator
+stack. It also provides concrete connections to the Audio service (for negotiating sessions shared
+between Audio and Sound Trigger and for notifications about audio recording) and to the various HAL
+factories.
+
+This is the only layer that makes strong assumptions about the environment instead of relying on
+abstractions.
+
+## Error Handling and Exception Conventions
+
+We follow conventions for usage of exceptions in the service, in order to correctly and consistently
+distinguish the following cases:
+
+1. The client has done something wrong.
+2. The service implementation has done something wrong.
+3. The HAL has done something wrong.
+4. Nobody has done anything wrong, but runtime conditions prevent an operation from being fulfilled
+  as intended.
+
+The `SoundTriggerMiddlewarePermission` class would reject any calls from unauthorized clients,
+responding with the appropriate exception.
+
+The `SoundTriggerMiddlewareValidation` class does much of this separation. By validating the
+client's data and state, it would throw a relevant `RuntimeException` exception to the client
+without passing the requests down to the lower layers. Once that is done, any exception thrown from
+the underlying implementation can be assumed to be not the client's fault. If caught, they will be
+classified according to the following rule:
+
+- If they are `RecoverableException`s, they represent category #4 above, and will be presented to
+  the client as `ServiceSpecificException`s with the same error code.
+- Otherwise, they are considered an internal error (including HAL malfunction) and will be
+  presented to the client as `ServiceSpecificException(Status.INTERNAL_ERROR)`.
+
+Internally, we would throw `RecoverableException` whenever appropriate. Whenever a HAL malfunctions,
+`SoundTriggerHalEnforcer` is responsible for rebooting it and throwing an exception. A HAL death is
+considered a valid failure mode, and thus result in `RecoverableException(Status.DEAD_OBJECT)`,
+which ends up as a `ServiceSpecificException(Status.DEAD_OBJECT)` on the client side.
+
+## Notes About Thread Synchronization
 This component has some tricky thread synchronization considerations due to its layered design and
 due to the fact that it is involved in both in-bound and out-bound calls from / to
-external components. To avoid potential deadlocks, a strict locking order must be ensured whenever
-nesting locks. The order is:
-- `SoundTriggerMiddlewareValidation` lock.
-- Audio policy service lock. This one is external - it should be assumed to be held whenever we're
-  inside the `ExternalCaptureStateTracker.setCaptureState()` call stack *AND* to be acquired from
-  within our calls into `AudioSessionProvider.acquireSession()`.
-- `SoundTriggerModule` lock.
+external components.
 
-This dictates careful consideration of callbacks going from `SoundTriggerModule` to
-`SoundTriggerMiddlewareValidation` and especially those coming from the `setCaptureState()` path.
-We always invoke those calls outside of the `SoundTriggerModule` lock, so we can lock
-`SoundTriggerMiddlewareValidation`. However, in the `setCaptureState()` case, we have to use atomics
-in `SoundTriggerMiddlewareValidation` and avoid the lock.
+The following mutexes need to be considered:
+- Typically, a one or more mutexes that exist in every layer of the sound trigger middleware stack
+  to serialize access to its internal state or to external components.
+- Audio Policy Service lock. This one is external - it should be assumed to be held whenever we're
+  inside the `ExternalCaptureStateTracker.setCaptureState()` call stack *AND* to be acquired from
+  within our calls into `AudioSessionProvider.acquireSession()` /
+  `AudioSessionProvider.releaseSession()`.
+
+To avoid potential deadlocks, a strict locking order must be ensured whenever nesting locks. The
+order is:
+- Upper layers of the stack, starting from the top (i.e. may not attempt to acquire a higher-layer
+  mutex while a lower-layer mutex is being held) until `ISoundTriggerHw2`.
+- Audio Policy Service lock.
+- Lower layers of the stack, starting from `ISoundTriggerHw2` all the way down to the HAL.
+
+In order to enforce this order, some conventions are established around when it is safe for a module
+to call another module, while having its local mutex(es) held:
+- Most calls (see exceptions below) originating from SoundTriggerMiddlewareService simply propagate
+  down the decorator stack. It is legal to call into the next layer down while holding a local
+  mutex. It is illegal to invoke a callback with a local mutex held.
+- Callbacks propagate from the lower layers up to the upper layers. It is legal to hold a local
+  mutex within a callback, but **not** while call to an upper layer.
+- In order to be able to synchronize, despite the asynchronous nature of callbacks,
+  `stopRecognition()` and `unloadModel()` work differently. They guarantee that once they return,
+  the callbacks associated with them will no longer be called. This implies that they have to block
+  until any pending callbacks are done processing and since these callbacks are potentially holding
+  locks of higher-order mutexes, we must not be holding a local mutex while calling down. The proper
+  sequence for these calls is:
+  - Obtain the local lock if needed. Update/check local state as necessary.
+  - Call the respective method of the delegate ("downwards"). Once it returns, not more callbacks
+    related to this operation will be called.
+  - Obtain the local lock if needed. Update local state as necessary. Assume that state might have
+    changed while the lock has been released.
+  - Release the local lock.
+  - Invoke any synchronous callbacks if needed.
+- Calling from `SoundTriggerMiddlewareImpl` / `SoundTriggerModule` into the audio policy service via
+  `acquireSession()` / `releaseSession()` while holding the local lock is legal.
+- `setCaptureState()` calls, originating from Audio Policy Service, into the lower layers of the
+  stack may call into the HAL (specifically, they must invoke `stopRecognition()`, but must not
+  block on callbacks. For this reason, `SoundTriggerHw2ConcurrentCaptureHandler`, which is the 
+  recipient of these calls, features a buffer and an additional thread, which allows the actual
+  stopping to be synchronous, as required, without having to block the call upon higher layers
+  processing the callbacks.
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandler.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandler.java
new file mode 100644
index 0000000..e3ce719
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandler.java
@@ -0,0 +1,456 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.soundtrigger_middleware;
+
+import android.annotation.NonNull;
+import android.media.permission.SafeCloseable;
+import android.media.soundtrigger.ModelParameterRange;
+import android.media.soundtrigger.PhraseRecognitionEvent;
+import android.media.soundtrigger.PhraseSoundModel;
+import android.media.soundtrigger.Properties;
+import android.media.soundtrigger.RecognitionConfig;
+import android.media.soundtrigger.RecognitionEvent;
+import android.media.soundtrigger.RecognitionStatus;
+import android.media.soundtrigger.SoundModel;
+import android.media.soundtrigger.SoundModelType;
+import android.media.soundtrigger.Status;
+import android.os.IBinder;
+
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Queue;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * This is a decorator around ISoundTriggerHal, which implements enforcement of concurrent capture
+ * constraints, for HAL implementations older than V2.4 (later versions support this feature at the
+ * HAL level).
+ * <p>
+ * Decorating an instance with this class would result in all active recognitions being aborted as
+ * soon as capture state becomes active. This class ensures consistent handling of abortions coming
+ * from that HAL and abortions coming from concurrent capture, in that only one abort event will be
+ * delivered, irrespective of the relative timing of the two events.
+ * <p>
+ * There are some delicate thread-safety issues handled here:
+ * <ul>
+ * <li>When a model is stopped via stopRecognition(), we guarantee that by the time the call
+ * returns, there will be no more recognition events (including abort) delivered for this model.
+ * This implies synchronous stopping and blocking until all pending events have been delivered.
+ * <li>When a model is stopped via onCaptureStateChange(true), the stopping of the recognition at
+ * the HAL level must be synchronous, but the call must not block on the delivery of the
+ * callbacks, due to the risk of a deadlock: the onCaptureStateChange() calls are typically
+ * invoked with the audio policy mutex held, so must not call method which may attempt to lock
+ * higher-level mutexes. See README.md in this directory for further details.
+ * </ul>
+ * The way this behavior is achieved is by having an additional thread with an event queue, which
+ * joins together model events coming from the delegate module with abort events originating from
+ * this layer (as result of external capture).
+ */
+public class SoundTriggerHalConcurrentCaptureHandler implements ISoundTriggerHal,
+        ICaptureStateNotifier.Listener {
+    private final @NonNull ISoundTriggerHal mDelegate;
+    private GlobalCallback mGlobalCallback;
+
+    /**
+     * Information about a model that is currently loaded. This is needed in order to be able to
+     * send abort events to its designated callback.
+     */
+    private static class LoadedModel {
+        final int type;
+        final @NonNull ModelCallback callback;
+
+        private LoadedModel(int type, @NonNull ModelCallback callback) {
+            this.type = type;
+            this.callback = callback;
+        }
+    }
+
+    /**
+     * This map holds the model type for every model that is loaded.
+     */
+    private final @NonNull Map<Integer, LoadedModel> mLoadedModels = new ConcurrentHashMap<>();
+
+    /**
+     * A set of all models that are currently active.
+     * We use this in order to know which models to stop in case of external capture.
+     * Used as a lock to synchronize operations that effect activity.
+     */
+    private final @NonNull Set<Integer> mActiveModels = new HashSet<>();
+
+    /**
+     * Notifier for changes in capture state.
+     */
+    private final @NonNull ICaptureStateNotifier mNotifier;
+
+    /**
+     * Whether capture is active.
+     */
+    private boolean mCaptureState;
+
+    /**
+     * Since we're wrapping the death recipient, we need to keep a translation map for unlinking.
+     * Key is the client recipient, value is the wrapper.
+     */
+    private final @NonNull Map<IBinder.DeathRecipient, IBinder.DeathRecipient>
+            mDeathRecipientMap = new ConcurrentHashMap<>();
+
+    private final @NonNull CallbackThread mCallbackThread = new CallbackThread();
+
+    public SoundTriggerHalConcurrentCaptureHandler(
+            @NonNull ISoundTriggerHal delegate,
+            @NonNull ICaptureStateNotifier notifier) {
+        mDelegate = delegate;
+        mNotifier = notifier;
+        mCaptureState = mNotifier.registerListener(this);
+    }
+
+    @Override
+    public void startRecognition(int modelHandle, int deviceHandle, int ioHandle,
+            RecognitionConfig config) {
+        synchronized (mActiveModels) {
+            if (mCaptureState) {
+                throw new RecoverableException(Status.RESOURCE_CONTENTION);
+            }
+            mDelegate.startRecognition(modelHandle, deviceHandle, ioHandle, config);
+            mActiveModels.add(modelHandle);
+        }
+    }
+
+    @Override
+    public void stopRecognition(int modelHandle) {
+        synchronized (mActiveModels) {
+            mDelegate.stopRecognition(modelHandle);
+            mActiveModels.remove(modelHandle);
+        }
+        // Block until all previous events are delivered. Since this is potentially blocking on
+        // upward calls, it must be done outside the lock.
+        mCallbackThread.flush();
+    }
+
+    @Override
+    public void onCaptureStateChange(boolean active) {
+        synchronized (mActiveModels) {
+            if (active) {
+                // Abort all active models. This must be done as one transaction to the event
+                // thread, in order to be able to dedupe events before they are delivered.
+                try (SafeCloseable ignored = mCallbackThread.stallReader()) {
+                    for (int modelHandle : mActiveModels) {
+                        mDelegate.stopRecognition(modelHandle);
+                        LoadedModel model = mLoadedModels.get(modelHandle);
+                        // An abort event must be the last one for its model.
+                        mCallbackThread.pushWithDedupe(modelHandle, true,
+                                () -> notifyAbort(modelHandle, model));
+                    }
+                }
+            } else {
+                mGlobalCallback.onResourcesAvailable();
+            }
+
+            mCaptureState = active;
+        }
+    }
+
+    @Override
+    public int loadSoundModel(SoundModel soundModel, ModelCallback callback) {
+        int handle = mDelegate.loadSoundModel(soundModel, new CallbackWrapper(callback));
+        mLoadedModels.put(handle, new LoadedModel(SoundModelType.GENERIC, callback));
+        return handle;
+    }
+
+    @Override
+    public int loadPhraseSoundModel(PhraseSoundModel soundModel,
+            ModelCallback callback) {
+        int handle = mDelegate.loadPhraseSoundModel(soundModel, new CallbackWrapper(callback));
+        mLoadedModels.put(handle, new LoadedModel(SoundModelType.KEYPHRASE, callback));
+        return handle;
+    }
+
+    @Override
+    public void unloadSoundModel(int modelHandle) {
+        mLoadedModels.remove(modelHandle);
+        mDelegate.unloadSoundModel(modelHandle);
+    }
+
+    @Override
+    public void registerCallback(GlobalCallback callback) {
+        mGlobalCallback = new GlobalCallback() {
+            @Override
+            public void onResourcesAvailable() {
+                mCallbackThread.push(callback::onResourcesAvailable);
+            }
+        };
+        mDelegate.registerCallback(mGlobalCallback);
+    }
+
+    @Override
+    public void linkToDeath(IBinder.DeathRecipient recipient) {
+        IBinder.DeathRecipient wrapper = new IBinder.DeathRecipient() {
+            @Override
+            public void binderDied() {
+                mCallbackThread.push(() -> recipient.binderDied());
+            }
+        };
+        mDelegate.linkToDeath(wrapper);
+        mDeathRecipientMap.put(recipient, wrapper);
+    }
+
+    @Override
+    public void unlinkToDeath(IBinder.DeathRecipient recipient) {
+        mDelegate.unlinkToDeath(mDeathRecipientMap.remove(recipient));
+    }
+
+    private class CallbackWrapper implements ISoundTriggerHal.ModelCallback {
+        private final @NonNull ISoundTriggerHal.ModelCallback mDelegateCallback;
+
+        private CallbackWrapper(@NonNull ModelCallback delegateCallback) {
+            mDelegateCallback = delegateCallback;
+        }
+
+        @Override
+        public void recognitionCallback(int modelHandle, RecognitionEvent event) {
+            // A recognition event must be the last one for its model, unless it is a forced one
+            // (those leave the model active).
+            mCallbackThread.pushWithDedupe(modelHandle,
+                    event.status != RecognitionStatus.FORCED,
+                    () -> mDelegateCallback.recognitionCallback(modelHandle, event));
+        }
+
+        @Override
+        public void phraseRecognitionCallback(int modelHandle, PhraseRecognitionEvent event) {
+            // A recognition event must be the last one for its model, unless it is a forced one
+            // (those leave the model active).
+            mCallbackThread.pushWithDedupe(modelHandle,
+                    event.common.status != RecognitionStatus.FORCED,
+                    () -> mDelegateCallback.phraseRecognitionCallback(modelHandle, event));
+        }
+
+        @Override
+        public void modelUnloaded(int modelHandle) {
+            mCallbackThread.push(() -> mDelegateCallback.modelUnloaded(modelHandle));
+        }
+    }
+
+    @Override
+    public void flushCallbacks() {
+        mDelegate.flushCallbacks();
+        mCallbackThread.flush();
+    }
+
+    /**
+     * This is a thread for asynchronous delivery of callback events, having the following features:
+     * <ul>
+     * <li>Events are processed on a separate thread than the thread that pushed them, in the order
+     * they were pushed.
+     * <li>Events can be deduped upon entry to the queue. This is achieved as follows:
+     * <ul>
+     *     <li>Temporarily stall the reader via {@link #stallReader()}.
+     *     <li>Within this scope, push as many events as needed via
+     *     {@link #pushWithDedupe(int, boolean, Runnable)}.
+     *     If an event with the same model handle as the one being pushed is already in the queue
+     *     and has been marked as "lastForModel", the new event will be discarded before entering
+     *     the queue.
+     *     <li>Finally, un-stall the reader by existing the scope.
+     *     <li>Events that do not require deduping can be pushed via {@link #push(Runnable)}.
+     * </ul>
+     * <li>Events can be flushed via {@link #flush()}. This will block until all events pushed prior
+     * to this call have been fully processed.
+     * </ul>
+     */
+    private static class CallbackThread {
+        private static class Entry {
+            final boolean lastForModel;
+            final int modelHandle;
+            final Runnable runnable;
+
+            private Entry(boolean lastForModel, int modelHandle, Runnable runnable) {
+                this.lastForModel = lastForModel;
+                this.modelHandle = modelHandle;
+                this.runnable = runnable;
+            }
+        }
+
+        private boolean mStallReader = false;
+        private final Queue<Entry> mList = new LinkedList<>();
+        private int mPushCount = 0;
+        private int mProcessedCount = 0;
+
+        /**
+         * Ctor. Starts the thread.
+         */
+        CallbackThread() {
+            new Thread(() -> {
+                try {
+                    while (true) {
+                        pop().run();
+                        synchronized (mList) {
+                            mProcessedCount++;
+                            mList.notifyAll();
+                        }
+                    }
+                } catch (InterruptedException e) {
+                    // If interrupted, exit.
+                }
+            }).start();
+        }
+
+        /**
+         * Push a new runnable to the queue, with no deduping.
+         *
+         * @param runnable The runnable to push.
+         */
+        void push(Runnable runnable) {
+            pushEntry(new Entry(false, 0, runnable), false);
+        }
+
+
+        /**
+         * Push a new runnable to the queue, with deduping.
+         * If an entry with the same model handle is already in the queue and was designated as
+         * last for model, this one will be discarded.
+         *
+         * @param modelHandle The model handle, used for deduping purposes.
+         * @param lastForModel If true, this entry will be considered the last one for this model
+         *                     and any subsequence calls for this handle (whether lastForModel or
+         *                     not) will be discarded while this entry is in the queue.
+         * @param runnable    The runnable to push.
+         */
+        void pushWithDedupe(int modelHandle, boolean lastForModel, Runnable runnable) {
+            pushEntry(new Entry(lastForModel, modelHandle, runnable), true);
+        }
+
+        /**
+         * Block until every entry pushed prior to this call has been processed.
+         */
+        void flush() {
+            try {
+                synchronized (mList) {
+                    int pushCount = mPushCount;
+                    while (mProcessedCount != pushCount) {
+                        mList.wait();
+                    }
+                }
+            } catch (InterruptedException ignored) {
+            }
+        }
+
+        /**
+         * Creates a scope (using a try-with-resources block), within which events that are pushed
+         * remain queued and processed. This is useful in order to utilize deduping.
+         */
+        SafeCloseable stallReader() {
+            synchronized (mList) {
+                mStallReader = true;
+                return () -> {
+                    synchronized (mList) {
+                        mStallReader = false;
+                        mList.notifyAll();
+                    }
+                };
+            }
+        }
+
+        private void pushEntry(Entry entry, boolean dedupe) {
+            synchronized (mList) {
+                if (dedupe) {
+                    for (Entry existing : mList) {
+                        if (existing.lastForModel && existing.modelHandle == entry.modelHandle) {
+                            return;
+                        }
+                    }
+                }
+                mList.add(entry);
+                mPushCount++;
+                mList.notifyAll();
+            }
+        }
+
+        private Runnable pop() throws InterruptedException {
+            synchronized (mList) {
+                while (mStallReader || mList.isEmpty()) {
+                    mList.wait();
+                }
+                return mList.remove().runnable;
+            }
+        }
+    }
+
+    /** Notify the client that recognition has been aborted. */
+    private static void notifyAbort(int modelHandle, LoadedModel model) {
+        switch (model.type) {
+            case SoundModelType.GENERIC: {
+                RecognitionEvent event = new RecognitionEvent();
+                event.status = RecognitionStatus.ABORTED;
+                event.type = SoundModelType.GENERIC;
+                model.callback.recognitionCallback(modelHandle, event);
+            }
+            break;
+
+            case SoundModelType.KEYPHRASE: {
+                PhraseRecognitionEvent event = new PhraseRecognitionEvent();
+                event.common.status = RecognitionStatus.ABORTED;
+                event.common.type = SoundModelType.KEYPHRASE;
+                model.callback.phraseRecognitionCallback(modelHandle, event);
+            }
+            break;
+        }
+    }
+
+    @Override
+    public void detach() {
+        mDelegate.detach();
+        mNotifier.unregisterListener(this);
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    // All methods below do trivial delegation - no interesting logic.
+    @Override
+    public void reboot() {
+        mDelegate.reboot();
+    }
+
+    @Override
+    public Properties getProperties() {
+        return mDelegate.getProperties();
+    }
+
+    @Override
+    public void forceRecognitionEvent(int modelHandle) {
+        mDelegate.forceRecognitionEvent(modelHandle);
+    }
+
+    @Override
+    public int getModelParameter(int modelHandle, int param) {
+        return mDelegate.getModelParameter(modelHandle, param);
+    }
+
+    @Override
+    public void setModelParameter(int modelHandle, int param, int value) {
+        mDelegate.setModelParameter(modelHandle, param, value);
+    }
+
+    @Override
+    public ModelParameterRange queryParameter(int modelHandle, int param) {
+        return mDelegate.queryParameter(modelHandle, param);
+    }
+
+    @Override
+    public String interfaceDescriptor() {
+        return mDelegate.interfaceDescriptor();
+    }
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHalEnforcer.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHalEnforcer.java
new file mode 100644
index 0000000..6870f4f
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHalEnforcer.java
@@ -0,0 +1,305 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.soundtrigger_middleware;
+
+import android.media.soundtrigger.ModelParameterRange;
+import android.media.soundtrigger.PhraseRecognitionEvent;
+import android.media.soundtrigger.PhraseSoundModel;
+import android.media.soundtrigger.Properties;
+import android.media.soundtrigger.RecognitionConfig;
+import android.media.soundtrigger.RecognitionEvent;
+import android.media.soundtrigger.RecognitionStatus;
+import android.media.soundtrigger.SoundModel;
+import android.media.soundtrigger.Status;
+import android.os.DeadObjectException;
+import android.os.IBinder;
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A decorator around a HAL, which adds some checks that the HAL is behaving as expected.
+ * This is not necessarily a strict enforcement for the HAL contract, but a place to add checks for
+ * common HAL malfunctions, to help track them and assist in debugging.
+ *
+ * The class is thread-safe.
+ */
+public class SoundTriggerHalEnforcer implements ISoundTriggerHal {
+    private static final String TAG = "SoundTriggerHalEnforcer";
+
+    /** The state of a model. */
+    private enum ModelState {
+        /** Model is loaded, but inactive. */
+        INACTIVE,
+        /** Model is active. */
+        ACTIVE,
+        /** A request to stop is being made, which may or may not have been processed yet. */
+        PENDING_STOP,
+    }
+
+    private final ISoundTriggerHal mUnderlying;
+    private final Map<Integer, ModelState> mModelStates = new HashMap<>();
+
+    public SoundTriggerHalEnforcer(
+            ISoundTriggerHal underlying) {
+        mUnderlying = underlying;
+    }
+
+    @Override
+    public Properties getProperties() {
+        try {
+            return mUnderlying.getProperties();
+        } catch (RuntimeException e) {
+            throw handleException(e);
+        }
+    }
+
+    @Override
+    public void registerCallback(GlobalCallback callback) {
+        try {
+            mUnderlying.registerCallback(callback);
+        } catch (RuntimeException e) {
+            throw handleException(e);
+        }
+    }
+
+    @Override
+    public int loadSoundModel(SoundModel soundModel, ModelCallback callback) {
+        try {
+            synchronized (mModelStates) {
+                int handle = mUnderlying.loadSoundModel(soundModel,
+                        new ModelCallbackEnforcer(callback));
+                mModelStates.put(handle, ModelState.INACTIVE);
+                return handle;
+            }
+        } catch (RuntimeException e) {
+            throw handleException(e);
+        }
+    }
+
+    @Override
+    public int loadPhraseSoundModel(PhraseSoundModel soundModel, ModelCallback callback) {
+        try {
+            synchronized (mModelStates) {
+                int handle = mUnderlying.loadPhraseSoundModel(soundModel,
+                        new ModelCallbackEnforcer(callback));
+                mModelStates.put(handle, ModelState.INACTIVE);
+                return handle;
+            }
+        } catch (RuntimeException e) {
+            throw handleException(e);
+        }
+    }
+
+    @Override
+    public void unloadSoundModel(int modelHandle) {
+        try {
+            // This call into the HAL may block on callback processing, thus must be done outside
+            // of the critical section. After this call returns we are guaranteed to no longer be
+            // getting unload events for that model.
+            mUnderlying.unloadSoundModel(modelHandle);
+            synchronized (mModelStates) {
+                // At this point, the model may have already been removed by a HAL callback, but the
+                // remove() method is a no-op in this case, so thus safe.
+                mModelStates.remove(modelHandle);
+            }
+        } catch (RuntimeException e) {
+            throw handleException(e);
+        }
+    }
+
+    @Override
+    public void stopRecognition(int modelHandle) {
+        try {
+            // This call into the HAL may block on callback processing, thus must be done outside
+            // of the critical section. After this call returns we are guaranteed to no longer be
+            // getting stop events for that model.
+            synchronized (mModelStates) {
+                mModelStates.replace(modelHandle, ModelState.PENDING_STOP);
+            }
+            mUnderlying.stopRecognition(modelHandle);
+            synchronized (mModelStates) {
+                // At this point, the model might have been preemptively unloaded, but replace()
+                // do nothing when the entry does not exist, so all good.
+                mModelStates.replace(modelHandle, ModelState.INACTIVE);
+            }
+        } catch (RuntimeException e) {
+            throw handleException(e);
+        }
+    }
+
+    @Override
+    public void startRecognition(int modelHandle, int deviceHandle, int ioHandle,
+            RecognitionConfig config) {
+        try {
+            synchronized (mModelStates) {
+                mUnderlying.startRecognition(modelHandle, deviceHandle, ioHandle, config);
+                mModelStates.replace(modelHandle, ModelState.ACTIVE);
+            }
+        } catch (RuntimeException e) {
+            throw handleException(e);
+        }
+    }
+
+    @Override
+    public void forceRecognitionEvent(int modelHandle) {
+        try {
+            mUnderlying.forceRecognitionEvent(modelHandle);
+        } catch (RuntimeException e) {
+            throw handleException(e);
+        }
+    }
+
+    @Override
+    public int getModelParameter(int modelHandle, int param) {
+        try {
+            return mUnderlying.getModelParameter(modelHandle, param);
+        } catch (RuntimeException e) {
+            throw handleException(e);
+        }
+    }
+
+    @Override
+    public void setModelParameter(int modelHandle, int param, int value) {
+        try {
+            mUnderlying.setModelParameter(modelHandle, param, value);
+        } catch (RuntimeException e) {
+            throw handleException(e);
+        }
+    }
+
+    @Override
+    public ModelParameterRange queryParameter(int modelHandle, int param) {
+        try {
+            return mUnderlying.queryParameter(modelHandle, param);
+        } catch (RuntimeException e) {
+            throw handleException(e);
+        }
+    }
+
+    @Override
+    public void linkToDeath(IBinder.DeathRecipient recipient) {
+        mUnderlying.linkToDeath(recipient);
+    }
+
+    @Override
+    public void unlinkToDeath(IBinder.DeathRecipient recipient) {
+        mUnderlying.unlinkToDeath(recipient);
+    }
+
+    @Override
+    public String interfaceDescriptor() {
+        return mUnderlying.interfaceDescriptor();
+    }
+
+    @Override
+    public void flushCallbacks() {
+        mUnderlying.flushCallbacks();
+    }
+
+    private RuntimeException handleException(RuntimeException e) {
+        if (e instanceof RecoverableException) {
+            throw e;
+        }
+        if (e.getCause() instanceof DeadObjectException) {
+            // Server is dead, no need to reboot.
+            Log.e(TAG, "HAL died");
+            throw new RecoverableException(Status.DEAD_OBJECT);
+        }
+        Log.e(TAG, "Exception caught from HAL, rebooting HAL");
+        reboot();
+        throw e;
+    }
+
+    @Override
+    public void reboot() {
+        mUnderlying.reboot();
+    }
+
+    @Override
+    public void detach() {
+        mUnderlying.detach();
+    }
+
+    private class ModelCallbackEnforcer implements ModelCallback {
+        private final ModelCallback mUnderlying;
+
+        private ModelCallbackEnforcer(
+                ModelCallback underlying) {
+            mUnderlying = underlying;
+        }
+
+        @Override
+        public void recognitionCallback(int model, RecognitionEvent event) {
+            int status = event.status;
+
+            synchronized (mModelStates) {
+                ModelState state = mModelStates.get(model);
+                if (state == null || state == ModelState.INACTIVE) {
+                    Log.wtfStack(TAG, "Unexpected recognition event for model: " + model);
+                    reboot();
+                    return;
+                }
+                if (status != RecognitionStatus.FORCED) {
+                    mModelStates.replace(model, ModelState.INACTIVE);
+                }
+            }
+            // Always invoke the delegate from outside the critical section.
+            mUnderlying.recognitionCallback(model, event);
+        }
+
+        @Override
+        public void phraseRecognitionCallback(int model, PhraseRecognitionEvent event) {
+            int status = event.common.status;
+            synchronized (mModelStates) {
+                ModelState state = mModelStates.get(model);
+                if (state == null || state == ModelState.INACTIVE) {
+                    Log.wtfStack(TAG, "Unexpected recognition event for model: " + model);
+                    reboot();
+                    return;
+                }
+                if (status != RecognitionStatus.FORCED) {
+                    mModelStates.replace(model, ModelState.INACTIVE);
+                }
+            }
+            // Always invoke the delegate from outside the critical section.
+            mUnderlying.phraseRecognitionCallback(model, event);
+        }
+
+        @Override
+        public void modelUnloaded(int modelHandle) {
+            synchronized (mModelStates) {
+                ModelState state = mModelStates.get(modelHandle);
+                if (state == null) {
+                    Log.wtfStack(TAG, "Unexpected unload event for model: " + modelHandle);
+                    reboot();
+                    return;
+                }
+
+                if (state == ModelState.ACTIVE) {
+                    Log.wtfStack(TAG, "Trying to unload an active model: " + modelHandle);
+                    reboot();
+                    return;
+                }
+                mModelStates.remove(modelHandle);
+            }
+            // Always invoke the delegate from outside the critical section.
+            mUnderlying.modelUnloaded(modelHandle);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHalMaxModelLimiter.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHalMaxModelLimiter.java
new file mode 100644
index 0000000..7dd28e0
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHalMaxModelLimiter.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.soundtrigger_middleware;
+
+import android.annotation.NonNull;
+import android.media.soundtrigger.ModelParameterRange;
+import android.media.soundtrigger.PhraseSoundModel;
+import android.media.soundtrigger.Properties;
+import android.media.soundtrigger.RecognitionConfig;
+import android.media.soundtrigger.SoundModel;
+import android.media.soundtrigger.Status;
+import android.os.IBinder;
+
+/**
+ * This is a decorator around ISoundTriggerHal, which implements enforcement of the maximum number
+ * of models supported by the HAL, for HAL implementations older than V2.4 that do not support
+ * rejection of model loading at the HAL layer.
+ * Since preemptive model unloading has been introduced in V2.4, it should never be used in
+ * conjunction with this class, hence we don't bother considering preemtive unloading when counting
+ * the number of currently loaded models.
+ */
+public class SoundTriggerHalMaxModelLimiter implements ISoundTriggerHal {
+    private final @NonNull ISoundTriggerHal mDelegate;
+    private final int mMaxModels;
+
+    // This counter is used to enforce the maximum number of loaded models.
+    private int mNumLoadedModels = 0;
+
+    private GlobalCallback mGlobalCallback;
+
+    public SoundTriggerHalMaxModelLimiter(
+            ISoundTriggerHal delegate, int maxModels) {
+        mDelegate = delegate;
+        this.mMaxModels = maxModels;
+    }
+
+    @Override
+    public void reboot() {
+        mDelegate.reboot();
+    }
+
+    @Override
+    public void detach() {
+        mDelegate.detach();
+    }
+
+    @Override
+    public Properties getProperties() {
+        return mDelegate.getProperties();
+    }
+
+    @Override
+    public void registerCallback(GlobalCallback callback) {
+        mGlobalCallback = callback;
+        mDelegate.registerCallback(mGlobalCallback);
+    }
+
+    @Override
+    public int loadSoundModel(SoundModel soundModel, ModelCallback callback) {
+        synchronized (this) {
+            if (mNumLoadedModels == mMaxModels) {
+                throw new RecoverableException(Status.RESOURCE_CONTENTION);
+            }
+            int result = mDelegate.loadSoundModel(soundModel, callback);
+            ++mNumLoadedModels;
+            return result;
+        }
+    }
+
+    @Override
+    public int loadPhraseSoundModel(PhraseSoundModel soundModel,
+            ModelCallback callback) {
+        synchronized (this) {
+            if (mNumLoadedModels == mMaxModels) {
+                throw new RecoverableException(Status.RESOURCE_CONTENTION);
+            }
+            int result = mDelegate.loadPhraseSoundModel(soundModel, callback);
+            ++mNumLoadedModels;
+            return result;
+        }
+    }
+
+    @Override
+    public void unloadSoundModel(int modelHandle) {
+        boolean wasAtMaxCapacity;
+        synchronized (this) {
+            wasAtMaxCapacity = mNumLoadedModels-- == mMaxModels;
+        }
+        try {
+            mDelegate.unloadSoundModel(modelHandle);
+        } catch (Exception e) {
+            synchronized (this) {
+                ++mNumLoadedModels;
+            }
+            throw e;
+        }
+        if (wasAtMaxCapacity) {
+            // It is legal to invoke callbacks from within unloadSoundModel().
+            // See README.md for details.
+            mGlobalCallback.onResourcesAvailable();
+        }
+    }
+
+    @Override
+    public void stopRecognition(int modelHandle) {
+        mDelegate.stopRecognition(modelHandle);
+    }
+
+    @Override
+    public void startRecognition(int modelHandle, int deviceHandle, int ioHandle,
+            RecognitionConfig config) {
+        mDelegate.startRecognition(modelHandle, deviceHandle, ioHandle, config);
+    }
+
+    @Override
+    public void forceRecognitionEvent(int modelHandle) {
+        mDelegate.forceRecognitionEvent(modelHandle);
+    }
+
+    @Override
+    public int getModelParameter(int modelHandle, int param) {
+        return mDelegate.getModelParameter(modelHandle, param);
+    }
+
+    @Override
+    public void setModelParameter(int modelHandle, int param, int value) {
+        mDelegate.setModelParameter(modelHandle, param, value);
+    }
+
+    @Override
+    public ModelParameterRange queryParameter(int modelHandle, int param) {
+        return mDelegate.queryParameter(modelHandle, param);
+    }
+
+    @Override
+    public void linkToDeath(IBinder.DeathRecipient recipient) {
+        mDelegate.linkToDeath(recipient);
+    }
+
+    @Override
+    public void unlinkToDeath(IBinder.DeathRecipient recipient) {
+        mDelegate.unlinkToDeath(recipient);
+    }
+
+    @Override
+    public String interfaceDescriptor() {
+        return mDelegate.interfaceDescriptor();
+    }
+
+    @Override
+    public void flushCallbacks() {
+        mDelegate.flushCallbacks();
+    }
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Watchdog.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHalWatchdog.java
similarity index 64%
rename from services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Watchdog.java
rename to services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHalWatchdog.java
index 212f81f..5fe06ee 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Watchdog.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHalWatchdog.java
@@ -17,13 +17,12 @@
 package com.android.server.soundtrigger_middleware;
 
 import android.annotation.NonNull;
-import android.hardware.soundtrigger.V2_1.ISoundTriggerHw;
-import android.hardware.soundtrigger.V2_3.ModelParameterRange;
-import android.hardware.soundtrigger.V2_3.Properties;
-import android.hardware.soundtrigger.V2_3.RecognitionConfig;
-import android.os.IHwBinder;
-import android.os.RemoteException;
-import android.os.SystemProperties;
+import android.media.soundtrigger.ModelParameterRange;
+import android.media.soundtrigger.PhraseSoundModel;
+import android.media.soundtrigger.Properties;
+import android.media.soundtrigger.RecognitionConfig;
+import android.media.soundtrigger.SoundModel;
+import android.os.IBinder;
 import android.util.Log;
 
 import java.util.Objects;
@@ -31,21 +30,19 @@
 import java.util.TimerTask;
 
 /**
- * An {@link ISoundTriggerHw2} decorator that would enforce deadlines on all calls and reboot the
+ * An {@link ISoundTriggerHal} decorator that would enforce deadlines on all calls and reboot the
  * HAL whenever they expire.
  */
-public class SoundTriggerHw2Watchdog implements ISoundTriggerHw2 {
+public class SoundTriggerHalWatchdog implements ISoundTriggerHal {
     private static final long TIMEOUT_MS = 3000;
-    private static final String TAG = "SoundTriggerHw2Watchdog";
+    private static final String TAG = "SoundTriggerHalWatchdog";
 
-    private final @NonNull
-    ISoundTriggerHw2 mUnderlying;
-    private final @NonNull
-    Timer mTimer;
+    private final @NonNull ISoundTriggerHal mUnderlying;
+    private final @NonNull Timer mTimer;
 
-    public SoundTriggerHw2Watchdog(@NonNull ISoundTriggerHw2 underlying) {
+    public SoundTriggerHalWatchdog(@NonNull ISoundTriggerHal underlying) {
         mUnderlying = Objects.requireNonNull(underlying);
-        mTimer = new Timer("SoundTriggerHw2Watchdog");
+        mTimer = new Timer("SoundTriggerHalWatchdog");
     }
 
     @Override
@@ -56,18 +53,24 @@
     }
 
     @Override
-    public int loadSoundModel(ISoundTriggerHw.SoundModel soundModel, Callback callback,
-            int cookie) {
+    public void registerCallback(GlobalCallback callback) {
         try (Watchdog ignore = new Watchdog()) {
-            return mUnderlying.loadSoundModel(soundModel, callback, cookie);
+            mUnderlying.registerCallback(callback);
         }
     }
 
     @Override
-    public int loadPhraseSoundModel(ISoundTriggerHw.PhraseSoundModel soundModel, Callback callback,
-            int cookie) {
+    public int loadSoundModel(SoundModel soundModel, ModelCallback callback) {
         try (Watchdog ignore = new Watchdog()) {
-            return mUnderlying.loadPhraseSoundModel(soundModel, callback, cookie);
+            return mUnderlying.loadSoundModel(soundModel, callback);
+        }
+    }
+
+    @Override
+    public int loadPhraseSoundModel(PhraseSoundModel soundModel,
+            ModelCallback callback) {
+        try (Watchdog ignore = new Watchdog()) {
+            return mUnderlying.loadPhraseSoundModel(soundModel, callback);
         }
     }
 
@@ -86,24 +89,17 @@
     }
 
     @Override
-    public void stopAllRecognitions() {
+    public void startRecognition(int modelHandle, int deviceHandle, int ioHandle,
+            RecognitionConfig config) {
         try (Watchdog ignore = new Watchdog()) {
-            mUnderlying.stopAllRecognitions();
+            mUnderlying.startRecognition(modelHandle, deviceHandle, ioHandle, config);
         }
     }
 
     @Override
-    public void startRecognition(int modelHandle, RecognitionConfig config, Callback callback,
-            int cookie) {
+    public void forceRecognitionEvent(int modelHandle) {
         try (Watchdog ignore = new Watchdog()) {
-            mUnderlying.startRecognition(modelHandle, config, callback, cookie);
-        }
-    }
-
-    @Override
-    public void getModelState(int modelHandle) {
-        try (Watchdog ignore = new Watchdog()) {
-            mUnderlying.getModelState(modelHandle);
+            mUnderlying.forceRecognitionEvent(modelHandle);
         }
     }
 
@@ -129,23 +125,33 @@
     }
 
     @Override
-    public boolean linkToDeath(IHwBinder.DeathRecipient recipient, long cookie) {
-        return mUnderlying.linkToDeath(recipient, cookie);
+    public void linkToDeath(IBinder.DeathRecipient recipient) {
+        mUnderlying.linkToDeath(recipient);
     }
 
     @Override
-    public boolean unlinkToDeath(IHwBinder.DeathRecipient recipient) {
-        return mUnderlying.unlinkToDeath(recipient);
+    public void unlinkToDeath(IBinder.DeathRecipient recipient) {
+        mUnderlying.unlinkToDeath(recipient);
     }
 
     @Override
-    public String interfaceDescriptor() throws RemoteException {
+    public String interfaceDescriptor() {
         return mUnderlying.interfaceDescriptor();
     }
 
-    private static void rebootHal() {
-        // This property needs to be defined in an init.rc script and trigger a HAL reboot.
-        SystemProperties.set("sys.audio.restart.hal", "1");
+    @Override
+    public void flushCallbacks() {
+        mUnderlying.flushCallbacks();
+    }
+
+    @Override
+    public void reboot() {
+        mUnderlying.reboot();
+    }
+
+    @Override
+    public void detach() {
+        mUnderlying.detach();
     }
 
     private class Watchdog implements AutoCloseable {
@@ -160,7 +166,7 @@
                 @Override
                 public void run() {
                     Log.e(TAG, "HAL deadline expired. Rebooting.", mException);
-                    rebootHal();
+                    reboot();
                 }
             };
             mTimer.schedule(mTask, TIMEOUT_MS);
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java
index 2f087f4..7a1f775 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java
@@ -18,60 +18,116 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.media.soundtrigger_middleware.Status;
+import android.hardware.soundtrigger.V2_0.ISoundTriggerHw;
+import android.media.soundtrigger.ModelParameterRange;
+import android.media.soundtrigger.PhraseSoundModel;
+import android.media.soundtrigger.Properties;
+import android.media.soundtrigger.RecognitionConfig;
+import android.media.soundtrigger.SoundModel;
+import android.media.soundtrigger.Status;
+import android.os.IBinder;
 import android.os.IHwBinder;
 import android.os.RemoteException;
+import android.system.OsConstants;
 
+import java.util.HashMap;
+import java.util.Map;
 import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicReference;
 
 /**
- * An implementation of {@link ISoundTriggerHw2}, on top of any
+ * An implementation of {@link ISoundTriggerHal}, on top of any
  * android.hardware.soundtrigger.V2_x.ISoundTriggerHw implementation. This class hides away some of
  * the details involved with retaining backward compatibility and adapts to the more pleasant syntax
- * exposed by {@link ISoundTriggerHw2}, compared to the bare driver interface.
+ * exposed by {@link ISoundTriggerHal}, compared to the bare driver interface.
  * <p>
  * Exception handling:
  * <ul>
  * <li>All {@link RemoteException}s get rethrown as {@link RuntimeException}.
  * <li>All HAL malfunctions get thrown as {@link HalException}.
  * <li>All unsupported operations get thrown as {@link RecoverableException} with a
- * {@link android.media.soundtrigger_middleware.Status#OPERATION_NOT_SUPPORTED}
+ * {@link android.media.soundtrigger.Status#OPERATION_NOT_SUPPORTED}
  * code.
  * </ul>
  */
-final class SoundTriggerHw2Compat implements ISoundTriggerHw2 {
-    private final @NonNull
-    IHwBinder mBinder;
-    private final @NonNull
-    android.hardware.soundtrigger.V2_0.ISoundTriggerHw mUnderlying_2_0;
-    private final @Nullable
-    android.hardware.soundtrigger.V2_1.ISoundTriggerHw mUnderlying_2_1;
-    private final @Nullable
-    android.hardware.soundtrigger.V2_2.ISoundTriggerHw mUnderlying_2_2;
-    private final @Nullable
-    android.hardware.soundtrigger.V2_3.ISoundTriggerHw mUnderlying_2_3;
+final class SoundTriggerHw2Compat implements ISoundTriggerHal {
+    private final @NonNull Runnable mRebootRunnable;
+    private final @NonNull IHwBinder mBinder;
+    private @NonNull android.hardware.soundtrigger.V2_0.ISoundTriggerHw mUnderlying_2_0;
+    private @Nullable android.hardware.soundtrigger.V2_1.ISoundTriggerHw mUnderlying_2_1;
+    private @Nullable android.hardware.soundtrigger.V2_2.ISoundTriggerHw mUnderlying_2_2;
+    private @Nullable android.hardware.soundtrigger.V2_3.ISoundTriggerHw mUnderlying_2_3;
+    private @Nullable android.hardware.soundtrigger.V2_4.ISoundTriggerHw mUnderlying_2_4;
 
-    public SoundTriggerHw2Compat(
-            @NonNull android.hardware.soundtrigger.V2_0.ISoundTriggerHw underlying) {
-        this(underlying.asBinder());
+    // HAL <=2.1 requires us to pass a callback argument to startRecognition. We will store the one
+    // passed on load and then pass it on start. We don't bother storing the callback on newer
+    // versions.
+    private final @NonNull ConcurrentMap<Integer, ModelCallback> mModelCallbacks =
+            new ConcurrentHashMap<>();
+
+    // A map from IBinder.DeathRecipient to IHwBinder.DeathRecipient for doing the mapping upon
+    // unlinking.
+    private final @NonNull Map<IBinder.DeathRecipient, IHwBinder.DeathRecipient>
+            mDeathRecipientMap = new HashMap<>();
+
+    // The properties are read at construction time and cached, since we need to use some of them
+    // to enforce constraints.
+    private final @NonNull Properties mProperties;
+
+    static ISoundTriggerHal create(
+            @NonNull ISoundTriggerHw underlying,
+            @NonNull Runnable rebootRunnable,
+            ICaptureStateNotifier notifier) {
+        return create(underlying.asBinder(), rebootRunnable, notifier);
     }
 
-    public SoundTriggerHw2Compat(IHwBinder binder) {
-        Objects.requireNonNull(binder);
+    static ISoundTriggerHal create(@NonNull IHwBinder binder,
+            @NonNull Runnable rebootRunnable,
+            ICaptureStateNotifier notifier) {
+        SoundTriggerHw2Compat compat = new SoundTriggerHw2Compat(binder, rebootRunnable);
+        ISoundTriggerHal result = compat;
+        // Add max model limiter for versions <2.4.
+        if (compat.mUnderlying_2_4 == null) {
+            result = new SoundTriggerHalMaxModelLimiter(result,
+                    compat.mProperties.maxSoundModels);
+        }
+        // Add concurrent capture handler for versions <2.4 which do not support concurrent capture.
+        if (compat.mUnderlying_2_4 == null && !compat.mProperties.concurrentCapture) {
+            result = new SoundTriggerHalConcurrentCaptureHandler(result, notifier);
+        }
+        return result;
+    }
 
-        mBinder = binder;
+    private SoundTriggerHw2Compat(@NonNull IHwBinder binder, @NonNull Runnable rebootRunnable) {
+        mRebootRunnable = Objects.requireNonNull(rebootRunnable);
+        mBinder = Objects.requireNonNull(binder);
+        initUnderlying(binder);
+        mProperties = Objects.requireNonNull(getPropertiesInternal());
+    }
 
+    private void initUnderlying(IHwBinder binder) {
         // We want to share the proxy instances rather than create a separate proxy for every
         // version, so we go down the versions in descending order to find the latest one supported,
         // and then simply up-cast it to obtain all the versions that are earlier.
 
+        // Attempt 2.4
+        android.hardware.soundtrigger.V2_4.ISoundTriggerHw as2_4 =
+                android.hardware.soundtrigger.V2_4.ISoundTriggerHw.asInterface(binder);
+        if (as2_4 != null) {
+            mUnderlying_2_0 =
+                    mUnderlying_2_1 = mUnderlying_2_2 = mUnderlying_2_3 = mUnderlying_2_4 = as2_4;
+            return;
+        }
+
         // Attempt 2.3
         android.hardware.soundtrigger.V2_3.ISoundTriggerHw as2_3 =
                 android.hardware.soundtrigger.V2_3.ISoundTriggerHw.asInterface(binder);
         if (as2_3 != null) {
             mUnderlying_2_0 = mUnderlying_2_1 = mUnderlying_2_2 = mUnderlying_2_3 = as2_3;
+            mUnderlying_2_4 = null;
             return;
         }
 
@@ -80,7 +136,7 @@
                 android.hardware.soundtrigger.V2_2.ISoundTriggerHw.asInterface(binder);
         if (as2_2 != null) {
             mUnderlying_2_0 = mUnderlying_2_1 = mUnderlying_2_2 = as2_2;
-            mUnderlying_2_3 = null;
+            mUnderlying_2_3 = mUnderlying_2_4 = null;
             return;
         }
 
@@ -89,7 +145,7 @@
                 android.hardware.soundtrigger.V2_1.ISoundTriggerHw.asInterface(binder);
         if (as2_1 != null) {
             mUnderlying_2_0 = mUnderlying_2_1 = as2_1;
-            mUnderlying_2_2 = mUnderlying_2_3 = null;
+            mUnderlying_2_2 = mUnderlying_2_3 = mUnderlying_2_4 = null;
             return;
         }
 
@@ -98,7 +154,7 @@
                 android.hardware.soundtrigger.V2_0.ISoundTriggerHw.asInterface(binder);
         if (as2_0 != null) {
             mUnderlying_2_0 = as2_0;
-            mUnderlying_2_1 = mUnderlying_2_2 = mUnderlying_2_3 = null;
+            mUnderlying_2_1 = mUnderlying_2_2 = mUnderlying_2_3 = mUnderlying_2_4 = null;
             return;
         }
 
@@ -111,12 +167,32 @@
         }
     }
 
+    private static void handleHalStatusAllowBusy(int status, String methodName) {
+        if (status == -OsConstants.EBUSY) {
+            throw new RecoverableException(Status.RESOURCE_CONTENTION);
+        }
+        handleHalStatus(status, methodName);
+    }
+
     @Override
-    public android.hardware.soundtrigger.V2_3.Properties getProperties() {
+    public void reboot() {
+        mRebootRunnable.run();
+    }
+
+    @Override
+    public void detach() {
+        // No-op.
+    }
+
+    @Override
+    public Properties getProperties() {
+        return mProperties;
+    }
+
+    private Properties getPropertiesInternal() {
         try {
             AtomicInteger retval = new AtomicInteger(-1);
-            AtomicReference<android.hardware.soundtrigger.V2_3.Properties>
-                    properties =
+            AtomicReference<android.hardware.soundtrigger.V2_3.Properties> properties =
                     new AtomicReference<>();
             try {
                 as2_3().getProperties_2_3(
@@ -129,30 +205,57 @@
                 return getProperties_2_0();
             }
             handleHalStatus(retval.get(), "getProperties_2_3");
-            return properties.get();
+            return ConversionUtil.hidl2aidlProperties(properties.get());
         } catch (RemoteException e) {
             throw e.rethrowAsRuntimeException();
         }
     }
 
     @Override
-    public int loadSoundModel(
-            android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel soundModel,
-            Callback callback, int cookie) {
+    public void registerCallback(GlobalCallback callback) {
+        try {
+            try {
+                as2_4().registerGlobalCallback(new GlobalCallbackWrapper(callback));
+            } catch (NotSupported e) {
+                // In versions < 2.4 the events represented by this callback don't exist, we can
+                // safely ignore this.
+            }
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    @Override
+    public int loadSoundModel(SoundModel soundModel, ModelCallback callback) {
+        android.hardware.soundtrigger.V2_3.ISoundTriggerHw.SoundModel hidlModel =
+                ConversionUtil.aidl2hidlSoundModel(soundModel);
         try {
             AtomicInteger retval = new AtomicInteger(-1);
             AtomicInteger handle = new AtomicInteger(0);
+
             try {
-                as2_1().loadSoundModel_2_1(soundModel, new SoundTriggerCallback(callback), cookie,
+                as2_4().loadSoundModel_2_4(hidlModel, new ModelCallbackWrapper(callback),
                         (r, h) -> {
                             retval.set(r);
                             handle.set(h);
                         });
+                handleHalStatusAllowBusy(retval.get(), "loadSoundModel_2_4");
             } catch (NotSupported e) {
-                // Fall-back to the 2.0 version:
-                return loadSoundModel_2_0(soundModel, callback, cookie);
+                // Fall-back to the 2.1 version:
+                try {
+                    as2_1().loadSoundModel_2_1(hidlModel, new ModelCallbackWrapper(callback),
+                            0,
+                            (r, h) -> {
+                                retval.set(r);
+                                handle.set(h);
+                            });
+                    handleHalStatus(retval.get(), "loadSoundModel_2_1");
+                    mModelCallbacks.put(handle.get(), callback);
+                } catch (NotSupported ee) {
+                    // Fall-back to the 2.0 version:
+                    return loadSoundModel_2_0(hidlModel, callback);
+                }
             }
-            handleHalStatus(retval.get(), "loadSoundModel_2_1");
             return handle.get();
         } catch (RemoteException e) {
             throw e.rethrowAsRuntimeException();
@@ -160,24 +263,35 @@
     }
 
     @Override
-    public int loadPhraseSoundModel(
-            android.hardware.soundtrigger.V2_1.ISoundTriggerHw.PhraseSoundModel soundModel,
-            Callback callback, int cookie) {
+    public int loadPhraseSoundModel(PhraseSoundModel soundModel, ModelCallback callback) {
+        android.hardware.soundtrigger.V2_1.ISoundTriggerHw.PhraseSoundModel hidlModel =
+                ConversionUtil.aidl2hidlPhraseSoundModel(soundModel);
         try {
             AtomicInteger retval = new AtomicInteger(-1);
             AtomicInteger handle = new AtomicInteger(0);
             try {
-                as2_1().loadPhraseSoundModel_2_1(soundModel, new SoundTriggerCallback(callback),
-                        cookie,
+                as2_4().loadPhraseSoundModel_2_4(hidlModel, new ModelCallbackWrapper(callback),
                         (r, h) -> {
                             retval.set(r);
                             handle.set(h);
                         });
+                handleHalStatusAllowBusy(retval.get(), "loadPhraseSoundModel_2_4");
             } catch (NotSupported e) {
-                // Fall-back to the 2.0 version:
-                return loadPhraseSoundModel_2_0(soundModel, callback, cookie);
+                // Fall-back to the 2.1 version:
+                try {
+                    as2_1().loadPhraseSoundModel_2_1(hidlModel, new ModelCallbackWrapper(callback),
+                            0,
+                            (r, h) -> {
+                                retval.set(r);
+                                handle.set(h);
+                            });
+                    handleHalStatus(retval.get(), "loadPhraseSoundModel_2_1");
+                    mModelCallbacks.put(handle.get(), callback);
+                } catch (NotSupported ee) {
+                    // Fall-back to the 2.0 version:
+                    return loadPhraseSoundModel_2_0(hidlModel, callback);
+                }
             }
-            handleHalStatus(retval.get(), "loadSoundModel_2_1");
             return handle.get();
         } catch (RemoteException e) {
             throw e.rethrowAsRuntimeException();
@@ -187,6 +301,8 @@
     @Override
     public void unloadSoundModel(int modelHandle) {
         try {
+            // Safe if key doesn't exist.
+            mModelCallbacks.remove(modelHandle);
             int retval = as2_0().unloadSoundModel(modelHandle);
             handleHalStatus(retval, "unloadSoundModel");
         } catch (RemoteException e) {
@@ -206,26 +322,23 @@
     }
 
     @Override
-    public void stopAllRecognitions() {
-        try {
-            int retval = as2_0().stopAllRecognitions();
-            handleHalStatus(retval, "stopAllRecognitions");
-        } catch (RemoteException e) {
-            throw e.rethrowAsRuntimeException();
-        }
-    }
-
-    @Override
-    public void startRecognition(int modelHandle,
-            android.hardware.soundtrigger.V2_3.RecognitionConfig config,
-            Callback callback, int cookie) {
+    public void startRecognition(int modelHandle, int deviceHandle, int ioHandle,
+            RecognitionConfig config) {
+        android.hardware.soundtrigger.V2_3.RecognitionConfig hidlConfig =
+                ConversionUtil.aidl2hidlRecognitionConfig(config, deviceHandle, ioHandle);
         try {
             try {
-                int retval = as2_3().startRecognition_2_3(modelHandle, config);
-                handleHalStatus(retval, "startRecognition_2_3");
+                int retval = as2_4().startRecognition_2_4(modelHandle, hidlConfig);
+                handleHalStatusAllowBusy(retval, "startRecognition_2_4");
             } catch (NotSupported e) {
-                // Fall-back to the 2.0 version:
-                startRecognition_2_1(modelHandle, config, callback, cookie);
+                // Fall-back to the 2.3 version:
+                try {
+                    int retval = as2_3().startRecognition_2_3(modelHandle, hidlConfig);
+                    handleHalStatus(retval, "startRecognition_2_3");
+                } catch (NotSupported ee) {
+                    // Fall-back to the 2.0 version:
+                    startRecognition_2_1(modelHandle, hidlConfig);
+                }
             }
         } catch (RemoteException e) {
             throw e.rethrowAsRuntimeException();
@@ -233,7 +346,7 @@
     }
 
     @Override
-    public void getModelState(int modelHandle) {
+    public void forceRecognitionEvent(int modelHandle) {
         try {
             int retval = as2_2().getModelState(modelHandle);
             handleHalStatus(retval, "getModelState");
@@ -276,8 +389,7 @@
     }
 
     @Override
-    public android.hardware.soundtrigger.V2_3.ModelParameterRange queryParameter(int modelHandle,
-            int param) {
+    public ModelParameterRange queryParameter(int modelHandle, int param) {
         AtomicInteger status = new AtomicInteger(-1);
         AtomicReference<android.hardware.soundtrigger.V2_3.OptionalModelParameterRange>
                 optionalRange =
@@ -298,25 +410,36 @@
         return (optionalRange.get().getDiscriminator()
                 == android.hardware.soundtrigger.V2_3.OptionalModelParameterRange.hidl_discriminator.range)
                 ?
-                optionalRange.get().range() : null;
+                ConversionUtil.hidl2aidlModelParameterRange(optionalRange.get().range()) : null;
     }
 
     @Override
-    public boolean linkToDeath(IHwBinder.DeathRecipient recipient, long cookie) {
-        return mBinder.linkToDeath(recipient, cookie);
+    public void linkToDeath(IBinder.DeathRecipient recipient) {
+        IHwBinder.DeathRecipient wrapper = cookie -> recipient.binderDied();
+        mDeathRecipientMap.put(recipient, wrapper);
+        mBinder.linkToDeath(wrapper, 0);
     }
 
     @Override
-    public boolean unlinkToDeath(IHwBinder.DeathRecipient recipient) {
-        return mBinder.unlinkToDeath(recipient);
+    public void unlinkToDeath(IBinder.DeathRecipient recipient) {
+        mBinder.unlinkToDeath(mDeathRecipientMap.remove(recipient));
     }
 
     @Override
-    public String interfaceDescriptor() throws RemoteException {
-        return as2_0().interfaceDescriptor();
+    public String interfaceDescriptor() {
+        try {
+            return as2_0().interfaceDescriptor();
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
     }
 
-    private android.hardware.soundtrigger.V2_3.Properties getProperties_2_0()
+    @Override
+    public void flushCallbacks() {
+        // This is a no-op. Only implemented for decorators.
+    }
+
+    private Properties getProperties_2_0()
             throws RemoteException {
         AtomicInteger retval = new AtomicInteger(-1);
         AtomicReference<android.hardware.soundtrigger.V2_0.ISoundTriggerHw.Properties>
@@ -328,12 +451,13 @@
                     properties.set(p);
                 });
         handleHalStatus(retval.get(), "getProperties");
-        return Hw2CompatUtil.convertProperties_2_0_to_2_3(properties.get());
+        return ConversionUtil.hidl2aidlProperties(
+                Hw2CompatUtil.convertProperties_2_0_to_2_3(properties.get()));
     }
 
     private int loadSoundModel_2_0(
             android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel soundModel,
-            Callback callback, int cookie)
+            ModelCallback callback)
             throws RemoteException {
         // Convert the soundModel to V2.0.
         android.hardware.soundtrigger.V2_0.ISoundTriggerHw.SoundModel model_2_0 =
@@ -341,17 +465,18 @@
 
         AtomicInteger retval = new AtomicInteger(-1);
         AtomicInteger handle = new AtomicInteger(0);
-        as2_0().loadSoundModel(model_2_0, new SoundTriggerCallback(callback), cookie, (r, h) -> {
+        as2_0().loadSoundModel(model_2_0, new ModelCallbackWrapper(callback), 0, (r, h) -> {
             retval.set(r);
             handle.set(h);
         });
         handleHalStatus(retval.get(), "loadSoundModel");
+        mModelCallbacks.put(handle.get(), callback);
         return handle.get();
     }
 
     private int loadPhraseSoundModel_2_0(
             android.hardware.soundtrigger.V2_1.ISoundTriggerHw.PhraseSoundModel soundModel,
-            Callback callback, int cookie)
+            ModelCallback callback)
             throws RemoteException {
         // Convert the soundModel to V2.0.
         android.hardware.soundtrigger.V2_0.ISoundTriggerHw.PhraseSoundModel model_2_0 =
@@ -359,28 +484,28 @@
 
         AtomicInteger retval = new AtomicInteger(-1);
         AtomicInteger handle = new AtomicInteger(0);
-        as2_0().loadPhraseSoundModel(model_2_0, new SoundTriggerCallback(callback), cookie,
+        as2_0().loadPhraseSoundModel(model_2_0, new ModelCallbackWrapper(callback), 0,
                 (r, h) -> {
                     retval.set(r);
                     handle.set(h);
                 });
         handleHalStatus(retval.get(), "loadSoundModel");
+        mModelCallbacks.put(handle.get(), callback);
         return handle.get();
     }
 
     private void startRecognition_2_1(int modelHandle,
-            android.hardware.soundtrigger.V2_3.RecognitionConfig config,
-            Callback callback, int cookie) {
+            android.hardware.soundtrigger.V2_3.RecognitionConfig config) {
         try {
             try {
                 android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig config_2_1 =
                         Hw2CompatUtil.convertRecognitionConfig_2_3_to_2_1(config);
                 int retval = as2_1().startRecognition_2_1(modelHandle, config_2_1,
-                        new SoundTriggerCallback(callback), cookie);
+                        new ModelCallbackWrapper(mModelCallbacks.get(modelHandle)), 0);
                 handleHalStatus(retval, "startRecognition_2_1");
             } catch (NotSupported e) {
                 // Fall-back to the 2.0 version:
-                startRecognition_2_0(modelHandle, config, callback, cookie);
+                startRecognition_2_0(modelHandle, config);
             }
         } catch (RemoteException e) {
             throw e.rethrowAsRuntimeException();
@@ -388,13 +513,12 @@
     }
 
     private void startRecognition_2_0(int modelHandle,
-            android.hardware.soundtrigger.V2_3.RecognitionConfig config,
-            Callback callback, int cookie)
+            android.hardware.soundtrigger.V2_3.RecognitionConfig config)
             throws RemoteException {
         android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig config_2_0 =
                 Hw2CompatUtil.convertRecognitionConfig_2_3_to_2_0(config);
         int retval = as2_0().startRecognition(modelHandle, config_2_0,
-                new SoundTriggerCallback(callback), cookie);
+                new ModelCallbackWrapper(mModelCallbacks.get(modelHandle)), 0);
         handleHalStatus(retval, "startRecognition");
     }
 
@@ -427,6 +551,14 @@
         return mUnderlying_2_3;
     }
 
+    private @NonNull
+    android.hardware.soundtrigger.V2_4.ISoundTriggerHw as2_4() throws NotSupported {
+        if (mUnderlying_2_4 == null) {
+            throw new NotSupported("Underlying driver version < 2.4");
+        }
+        return mUnderlying_2_4;
+    }
+
     /**
      * A checked exception representing the requested interface version not being supported.
      * At the public interface layer, use {@link #throwAsRecoverableException()} to propagate it to
@@ -448,28 +580,48 @@
         }
     }
 
-    private static class SoundTriggerCallback extends
-            android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.Stub {
-        private final @NonNull
-        Callback mDelegate;
+    private static class GlobalCallbackWrapper extends
+            android.hardware.soundtrigger.V2_4.ISoundTriggerHwGlobalCallback.Stub {
+        private final @NonNull GlobalCallback mDelegate;
 
-        private SoundTriggerCallback(
-                @NonNull Callback delegate) {
+        private GlobalCallbackWrapper(@NonNull GlobalCallback delegate) {
+            mDelegate = delegate;
+        }
+
+        @Override
+        public void onResourcesAvailable() {
+            mDelegate.onResourcesAvailable();
+        }
+    }
+
+    private static class ModelCallbackWrapper extends
+            android.hardware.soundtrigger.V2_4.ISoundTriggerHwCallback.Stub {
+        private final @NonNull ModelCallback mDelegate;
+
+        private ModelCallbackWrapper(
+                @NonNull ModelCallback delegate) {
             mDelegate = Objects.requireNonNull(delegate);
         }
 
         @Override
+        public void modelUnloaded(int modelHandle) {
+            mDelegate.modelUnloaded(modelHandle);
+        }
+
+        @Override
         public void recognitionCallback_2_1(
                 android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.RecognitionEvent event,
                 int cookie) {
-            mDelegate.recognitionCallback(event, cookie);
+            mDelegate.recognitionCallback(event.header.model,
+                    ConversionUtil.hidl2aidlRecognitionEvent(event));
         }
 
         @Override
         public void phraseRecognitionCallback_2_1(
                 android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.PhraseRecognitionEvent event,
                 int cookie) {
-            mDelegate.phraseRecognitionCallback(event, cookie);
+            mDelegate.phraseRecognitionCallback(event.common.header.model,
+                    ConversionUtil.hidl2aidlPhraseRecognitionEvent(event));
         }
 
         @Override
@@ -485,7 +637,7 @@
                 int cookie) {
             android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.RecognitionEvent event_2_1 =
                     Hw2CompatUtil.convertRecognitionEvent_2_0_to_2_1(event);
-            mDelegate.recognitionCallback(event_2_1, cookie);
+            recognitionCallback_2_1(event_2_1, cookie);
         }
 
         @Override
@@ -494,7 +646,7 @@
                 int cookie) {
             android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.PhraseRecognitionEvent
                     event_2_1 = Hw2CompatUtil.convertPhraseRecognitionEvent_2_0_to_2_1(event);
-            mDelegate.phraseRecognitionCallback(event_2_1, cookie);
+            phraseRecognitionCallback_2_1(event_2_1, cookie);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Enforcer.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Enforcer.java
deleted file mode 100644
index cf7460b..0000000
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Enforcer.java
+++ /dev/null
@@ -1,257 +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.server.soundtrigger_middleware;
-
-import android.hardware.soundtrigger.V2_1.ISoundTriggerHw;
-import android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback;
-import android.hardware.soundtrigger.V2_3.ModelParameterRange;
-import android.hardware.soundtrigger.V2_3.Properties;
-import android.hardware.soundtrigger.V2_3.RecognitionConfig;
-import android.media.soundtrigger_middleware.Status;
-import android.os.DeadObjectException;
-import android.os.IHwBinder;
-import android.os.RemoteException;
-import android.os.SystemProperties;
-import android.util.Log;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * A decorator around a HAL, which adds some checks that the HAL is behaving as expected.
- * This is not necessarily a strict enforcement for the HAL contract, but a place to add checks for
- * common HAL malfunctions, to help track them and assist in debugging.
- *
- * The class is thread-safe.
- */
-public class SoundTriggerHw2Enforcer implements ISoundTriggerHw2 {
-    static final String TAG = "SoundTriggerHw2Enforcer";
-
-    final ISoundTriggerHw2 mUnderlying;
-    Map<Integer, Boolean> mModelStates = new HashMap<>();
-
-    public SoundTriggerHw2Enforcer(
-            ISoundTriggerHw2 underlying) {
-        mUnderlying = underlying;
-    }
-
-    @Override
-    public Properties getProperties() {
-        try {
-            return mUnderlying.getProperties();
-        } catch (RuntimeException e) {
-            throw handleException(e);
-        }
-    }
-
-    @Override
-    public int loadSoundModel(ISoundTriggerHw.SoundModel soundModel, Callback callback,
-            int cookie) {
-        try {
-            int handle = mUnderlying.loadSoundModel(soundModel, new CallbackEnforcer(callback),
-                    cookie);
-            synchronized (mModelStates) {
-                mModelStates.put(handle, false);
-            }
-            return handle;
-        } catch (RuntimeException e) {
-            throw handleException(e);
-        }
-    }
-
-    @Override
-    public int loadPhraseSoundModel(ISoundTriggerHw.PhraseSoundModel soundModel, Callback callback,
-            int cookie) {
-        try {
-            int handle = mUnderlying.loadPhraseSoundModel(soundModel,
-                    new CallbackEnforcer(callback),
-                    cookie);
-            synchronized (mModelStates) {
-                mModelStates.put(handle, false);
-            }
-            return handle;
-        } catch (RuntimeException e) {
-            throw handleException(e);
-        }
-    }
-
-    @Override
-    public void unloadSoundModel(int modelHandle) {
-        try {
-            mUnderlying.unloadSoundModel(modelHandle);
-            synchronized (mModelStates) {
-                mModelStates.remove(modelHandle);
-            }
-        } catch (RuntimeException e) {
-            throw handleException(e);
-        }
-    }
-
-    @Override
-    public void stopRecognition(int modelHandle) {
-        try {
-            mUnderlying.stopRecognition(modelHandle);
-            synchronized (mModelStates) {
-                mModelStates.replace(modelHandle, false);
-            }
-        } catch (RuntimeException e) {
-            throw handleException(e);
-        }
-    }
-
-    @Override
-    public void stopAllRecognitions() {
-        try {
-            mUnderlying.stopAllRecognitions();
-            synchronized (mModelStates) {
-                for (Map.Entry<Integer, Boolean> entry : mModelStates.entrySet()) {
-                    entry.setValue(false);
-                }
-            }
-        } catch (RuntimeException e) {
-            throw handleException(e);
-        }
-    }
-
-    @Override
-    public void startRecognition(int modelHandle, RecognitionConfig config, Callback callback,
-            int cookie) {
-        // It is possible that an event will be sent before the HAL returns from the
-        // startRecognition call, thus it is important to set the state to active before the call.
-        synchronized (mModelStates) {
-            mModelStates.replace(modelHandle, true);
-        }
-        try {
-            mUnderlying.startRecognition(modelHandle, config, new CallbackEnforcer(callback),
-                    cookie);
-        } catch (RuntimeException e) {
-            throw handleException(e);
-        }
-    }
-
-    @Override
-    public void getModelState(int modelHandle) {
-        try {
-            mUnderlying.getModelState(modelHandle);
-        } catch (RuntimeException e) {
-            throw handleException(e);
-        }
-    }
-
-    @Override
-    public int getModelParameter(int modelHandle, int param) {
-        try {
-            return mUnderlying.getModelParameter(modelHandle, param);
-        } catch (RuntimeException e) {
-            throw handleException(e);
-        }
-    }
-
-    @Override
-    public void setModelParameter(int modelHandle, int param, int value) {
-        try {
-            mUnderlying.setModelParameter(modelHandle, param, value);
-        } catch (RuntimeException e) {
-            throw handleException(e);
-        }
-    }
-
-    @Override
-    public ModelParameterRange queryParameter(int modelHandle, int param) {
-        try {
-            return mUnderlying.queryParameter(modelHandle, param);
-        } catch (RuntimeException e) {
-            throw handleException(e);
-        }
-    }
-
-    @Override
-    public boolean linkToDeath(IHwBinder.DeathRecipient recipient, long cookie) {
-        return mUnderlying.linkToDeath(recipient, cookie);
-    }
-
-    @Override
-    public boolean unlinkToDeath(IHwBinder.DeathRecipient recipient) {
-        return mUnderlying.unlinkToDeath(recipient);
-    }
-
-    @Override
-    public String interfaceDescriptor() throws RemoteException {
-        return mUnderlying.interfaceDescriptor();
-    }
-
-    private static RuntimeException handleException(RuntimeException e) {
-        if (e.getCause() instanceof DeadObjectException) {
-            // Server is dead, no need to reboot.
-            Log.e(TAG, "HAL died");
-            throw new RecoverableException(Status.DEAD_OBJECT);
-        }
-        Log.e(TAG, "Exception caught from HAL, rebooting HAL");
-        rebootHal();
-        throw e;
-    }
-
-    private static void rebootHal() {
-        // This property needs to be defined in an init.rc script and trigger a HAL reboot.
-        SystemProperties.set("sys.audio.restart.hal", "1");
-    }
-
-    private class CallbackEnforcer implements Callback {
-        private final Callback mUnderlying;
-
-        private CallbackEnforcer(
-                Callback underlying) {
-            mUnderlying = underlying;
-        }
-
-        @Override
-        public void recognitionCallback(ISoundTriggerHwCallback.RecognitionEvent event,
-                int cookie) {
-            int model = event.header.model;
-            synchronized (mModelStates) {
-                if (!mModelStates.getOrDefault(model, false)) {
-                    Log.wtfStack(TAG, "Unexpected recognition event for model: " + model);
-                    rebootHal();
-                    return;
-                }
-                if (event.header.status
-                        != android.media.soundtrigger_middleware.RecognitionStatus.FORCED) {
-                    mModelStates.replace(model, false);
-                }
-            }
-            mUnderlying.recognitionCallback(event, cookie);
-        }
-
-        @Override
-        public void phraseRecognitionCallback(ISoundTriggerHwCallback.PhraseRecognitionEvent event,
-                int cookie) {
-            int model = event.common.header.model;
-            synchronized (mModelStates) {
-                if (!mModelStates.getOrDefault(model, false)) {
-                    Log.wtfStack(TAG, "Unexpected recognition event for model: " + model);
-                    rebootHal();
-                    return;
-                }
-                if (event.common.header.status
-                        != android.media.soundtrigger_middleware.RecognitionStatus.FORCED) {
-                    mModelStates.replace(model, false);
-                }
-            }
-            mUnderlying.phraseRecognitionCallback(event, cookie);
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw3Compat.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw3Compat.java
new file mode 100644
index 0000000..f564756
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw3Compat.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.soundtrigger_middleware;
+
+import android.annotation.NonNull;
+import android.hardware.soundtrigger3.ISoundTriggerHw;
+import android.hardware.soundtrigger3.ISoundTriggerHwCallback;
+import android.hardware.soundtrigger3.ISoundTriggerHwGlobalCallback;
+import android.media.soundtrigger.ModelParameterRange;
+import android.media.soundtrigger.PhraseRecognitionEvent;
+import android.media.soundtrigger.PhraseSoundModel;
+import android.media.soundtrigger.Properties;
+import android.media.soundtrigger.RecognitionConfig;
+import android.media.soundtrigger.RecognitionEvent;
+import android.media.soundtrigger.SoundModel;
+import android.media.soundtrigger.Status;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+
+public class SoundTriggerHw3Compat implements ISoundTriggerHal {
+    private final @NonNull ISoundTriggerHw mDriver;
+    private final @NonNull Runnable mRebootRunnable;
+
+    public SoundTriggerHw3Compat(@NonNull IBinder binder, @NonNull Runnable rebootRunnable) {
+        mDriver = android.hardware.soundtrigger3.ISoundTriggerHw.Stub.asInterface(binder);
+        mRebootRunnable = rebootRunnable;
+    }
+
+    @Override
+    public Properties getProperties() {
+        try {
+            return mDriver.getProperties();
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    @Override
+    public void registerCallback(GlobalCallback callback) {
+        try {
+            mDriver.registerGlobalCallback(new GlobalCallbackAdaper(callback));
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    @Override
+    public int loadSoundModel(SoundModel soundModel, ModelCallback callback) {
+        try {
+            return mDriver.loadSoundModel(soundModel, new ModelCallbackAdaper(callback));
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        } catch (ServiceSpecificException e) {
+            if (e.errorCode == Status.RESOURCE_CONTENTION) {
+                throw new RecoverableException(Status.RESOURCE_CONTENTION);
+            }
+            throw e;
+        }
+    }
+
+    @Override
+    public int loadPhraseSoundModel(PhraseSoundModel soundModel, ModelCallback callback) {
+        try {
+            return mDriver.loadPhraseSoundModel(soundModel, new ModelCallbackAdaper(callback));
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        } catch (ServiceSpecificException e) {
+            if (e.errorCode == Status.RESOURCE_CONTENTION) {
+                throw new RecoverableException(Status.RESOURCE_CONTENTION);
+            }
+            throw e;
+        }
+    }
+
+    @Override
+    public void unloadSoundModel(int modelHandle) {
+        try {
+            mDriver.unloadSoundModel(modelHandle);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    @Override
+    public void startRecognition(int modelHandle, int deviceHandle, int ioHandle,
+            RecognitionConfig config) {
+        try {
+            mDriver.startRecognition(modelHandle, deviceHandle, ioHandle, config);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        } catch (ServiceSpecificException e) {
+            if (e.errorCode == Status.RESOURCE_CONTENTION) {
+                throw new RecoverableException(Status.RESOURCE_CONTENTION);
+            }
+            throw e;
+        }
+    }
+
+    @Override
+    public void stopRecognition(int modelHandle) {
+        try {
+            mDriver.stopRecognition(modelHandle);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    @Override
+    public void forceRecognitionEvent(int modelHandle) {
+        try {
+            mDriver.forceRecognitionEvent(modelHandle);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    @Override
+    public ModelParameterRange queryParameter(int modelHandle, int param) {
+        try {
+            return mDriver.queryParameter(modelHandle, param);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    @Override
+    public int getModelParameter(int modelHandle, int param) {
+        try {
+            return mDriver.getParameter(modelHandle, param);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    @Override
+    public void setModelParameter(int modelHandle, int param, int value) {
+        try {
+            mDriver.setParameter(modelHandle, param, value);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    @Override
+    public String interfaceDescriptor() {
+        try {
+            return mDriver.asBinder().getInterfaceDescriptor();
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    @Override
+    public void linkToDeath(IBinder.DeathRecipient recipient) {
+        try {
+            mDriver.asBinder().linkToDeath(recipient, 0);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    @Override
+    public void unlinkToDeath(IBinder.DeathRecipient recipient) {
+        mDriver.asBinder().unlinkToDeath(recipient, 0);
+    }
+
+    @Override
+    public void flushCallbacks() {
+        // No-op.
+    }
+
+    @Override
+    public void reboot() {
+        mRebootRunnable.run();
+    }
+
+    @Override
+    public void detach() {
+        // No-op.
+    }
+
+    private static class GlobalCallbackAdaper extends ISoundTriggerHwGlobalCallback.Stub {
+        private final @NonNull GlobalCallback mDelegate;
+
+        public GlobalCallbackAdaper(@NonNull GlobalCallback callback) {
+            mDelegate = callback;
+        }
+
+        @Override
+        public void onResourcesAvailable() {
+            mDelegate.onResourcesAvailable();
+        }
+    }
+
+    private static class ModelCallbackAdaper extends ISoundTriggerHwCallback.Stub {
+        private final @NonNull ModelCallback mDelegate;
+
+        public ModelCallbackAdaper(ModelCallback callback) {
+            mDelegate = callback;
+        }
+
+        @Override
+        public void modelUnloaded(int model) {
+            mDelegate.modelUnloaded(model);
+        }
+
+        @Override
+        public void phraseRecognitionCallback(int model, PhraseRecognitionEvent event) {
+            mDelegate.phraseRecognitionCallback(model, event);
+        }
+
+        @Override
+        public void recognitionCallback(int model, RecognitionEvent event) {
+            mDelegate.recognitionCallback(model, event);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java
index d76b1bf..c8c0f3d 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java
@@ -17,12 +17,9 @@
 package com.android.server.soundtrigger_middleware;
 
 import android.annotation.NonNull;
-import android.hardware.soundtrigger.V2_0.ISoundTriggerHw;
 import android.media.soundtrigger_middleware.ISoundTriggerCallback;
-import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
 import android.media.soundtrigger_middleware.ISoundTriggerModule;
 import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
-import android.os.IBinder;
 import android.util.Log;
 
 import java.util.ArrayList;
@@ -39,7 +36,7 @@
  * <li>There is no binder instance associated with this implementation. Do not call asBinder().
  * <li>The implementation may throw a {@link RecoverableException} to indicate non-fatal,
  * recoverable faults. The error code would one of the
- * {@link android.media.soundtrigger_middleware.Status}
+ * {@link android.media.soundtrigger.Status}
  * constants. Any other exception thrown should be regarded as a bug in the implementation or one
  * of its dependencies (assuming correct usage).
  * <li>The implementation is designed for testibility by featuring dependency injection (the
@@ -84,15 +81,15 @@
             @NonNull AudioSessionProvider audioSessionProvider) {
         List<SoundTriggerModule> modules = new ArrayList<>(halFactories.length);
 
-        for (int i = 0; i < halFactories.length; ++i) {
+        for (HalFactory halFactory : halFactories) {
             try {
-                modules.add(new SoundTriggerModule(halFactories[i], audioSessionProvider));
+                modules.add(new SoundTriggerModule(halFactory, audioSessionProvider));
             } catch (Exception e) {
                 Log.e(TAG, "Failed to add a SoundTriggerModule instance", e);
             }
         }
 
-        mModules = modules.toArray(new SoundTriggerModule[modules.size()]);
+        mModules = modules.toArray(new SoundTriggerModule[0]);
     }
 
     /**
@@ -122,18 +119,4 @@
     ISoundTriggerModule attach(int handle, @NonNull ISoundTriggerCallback callback) {
         return mModules[handle].attach(callback);
     }
-
-    @Override
-    public void setCaptureState(boolean active) {
-        for (SoundTriggerModule module : mModules) {
-            module.setExternalCaptureState(active);
-        }
-    }
-
-    @Override
-    public @NonNull
-    IBinder asBinder() {
-        throw new UnsupportedOperationException(
-                "This implementation is not inteded to be used directly with Binder.");
-    }
-}
\ No newline at end of file
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java
index 2ef0759..559e777 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java
@@ -18,16 +18,16 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.media.soundtrigger.ModelParameterRange;
+import android.media.soundtrigger.PhraseRecognitionEvent;
+import android.media.soundtrigger.PhraseSoundModel;
+import android.media.soundtrigger.RecognitionConfig;
+import android.media.soundtrigger.RecognitionEvent;
+import android.media.soundtrigger.SoundModel;
 import android.media.permission.Identity;
 import android.media.permission.IdentityContext;
 import android.media.soundtrigger_middleware.ISoundTriggerCallback;
 import android.media.soundtrigger_middleware.ISoundTriggerModule;
-import android.media.soundtrigger_middleware.ModelParameterRange;
-import android.media.soundtrigger_middleware.PhraseRecognitionEvent;
-import android.media.soundtrigger_middleware.PhraseSoundModel;
-import android.media.soundtrigger_middleware.RecognitionConfig;
-import android.media.soundtrigger_middleware.RecognitionEvent;
-import android.media.soundtrigger_middleware.SoundModel;
 import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -57,9 +57,10 @@
  *     }
  * }
  * </code></pre>
- * The actual handling of these events is then done inside of {@link #logReturnWithObject(Object,
- * String, Object, Object[])}, {@link #logVoidReturnWithObject(Object, String, Object[])} and {@link
- * #logExceptionWithObject(Object, String, Exception, Object[])}.
+ * The actual handling of these events is then done inside of
+ * {@link #logReturnWithObject(Object, Identity, String, Object, Object[])},
+ * {@link #logVoidReturnWithObject(Object, Identity, String, Object[])} and {@link
+ * #logExceptionWithObject(Object, Identity, String, Exception, Object[])}.
  */
 public class SoundTriggerMiddlewareLogging implements ISoundTriggerMiddlewareInternal, Dumpable {
     private static final String TAG = "SoundTriggerMiddlewareLogging";
@@ -96,23 +97,6 @@
         }
     }
 
-    @Override
-    public void setCaptureState(boolean active) throws RemoteException {
-        try {
-            mDelegate.setCaptureState(active);
-            logVoidReturn("setCaptureState", active);
-        } catch (Exception e) {
-            logException("setCaptureState", e, active);
-            throw e;
-        }
-    }
-
-    @Override
-    public IBinder asBinder() {
-        throw new UnsupportedOperationException(
-                "This implementation is not inteded to be used directly with Binder.");
-    }
-
     // Override toString() in order to have the delegate's ID in it.
     @Override
     public String toString() {
@@ -298,10 +282,10 @@
             }
 
             @Override
-            public void onRecognition(int modelHandle, RecognitionEvent event)
+            public void onRecognition(int modelHandle, RecognitionEvent event, int captureSession)
                     throws RemoteException {
                 try {
-                    mCallbackDelegate.onRecognition(modelHandle, event);
+                    mCallbackDelegate.onRecognition(modelHandle, event, captureSession);
                     logVoidReturn("onRecognition", modelHandle, event);
                 } catch (Exception e) {
                     logException("onRecognition", e, modelHandle, event);
@@ -310,10 +294,11 @@
             }
 
             @Override
-            public void onPhraseRecognition(int modelHandle, PhraseRecognitionEvent event)
+            public void onPhraseRecognition(int modelHandle, PhraseRecognitionEvent event,
+                    int captureSession)
                     throws RemoteException {
                 try {
-                    mCallbackDelegate.onPhraseRecognition(modelHandle, event);
+                    mCallbackDelegate.onPhraseRecognition(modelHandle, event, captureSession);
                     logVoidReturn("onPhraseRecognition", modelHandle, event);
                 } catch (Exception e) {
                     logException("onPhraseRecognition", e, modelHandle, event);
@@ -322,12 +307,23 @@
             }
 
             @Override
-            public void onRecognitionAvailabilityChange(boolean available) throws RemoteException {
+            public void onModelUnloaded(int modelHandle) throws RemoteException {
                 try {
-                    mCallbackDelegate.onRecognitionAvailabilityChange(available);
-                    logVoidReturn("onRecognitionAvailabilityChange", available);
+                    mCallbackDelegate.onModelUnloaded(modelHandle);
+                    logVoidReturn("onModelUnloaded", modelHandle);
                 } catch (Exception e) {
-                    logException("onRecognitionAvailabilityChange", e, available);
+                    logException("onModelUnloaded", e, modelHandle);
+                    throw e;
+                }
+            }
+
+            @Override
+            public void onResourcesAvailable() throws RemoteException {
+                try {
+                    mCallbackDelegate.onResourcesAvailable();
+                    logVoidReturn("onResourcesAvailable");
+                } catch (Exception e) {
+                    logException("onResourcesAvailable", e);
                     throw e;
                 }
             }
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewarePermission.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewarePermission.java
index d5ab574b..613f334 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewarePermission.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewarePermission.java
@@ -23,20 +23,20 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.PermissionChecker;
+import android.media.soundtrigger.ModelParameterRange;
+import android.media.soundtrigger.PhraseRecognitionEvent;
+import android.media.soundtrigger.PhraseSoundModel;
+import android.media.soundtrigger.RecognitionConfig;
+import android.media.soundtrigger.RecognitionEvent;
+import android.media.soundtrigger.SoundModel;
+import android.media.soundtrigger.Status;
 import android.media.permission.Identity;
 import android.media.permission.IdentityContext;
 import android.media.permission.PermissionUtil;
 import android.media.soundtrigger_middleware.ISoundTriggerCallback;
 import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
 import android.media.soundtrigger_middleware.ISoundTriggerModule;
-import android.media.soundtrigger_middleware.ModelParameterRange;
-import android.media.soundtrigger_middleware.PhraseRecognitionEvent;
-import android.media.soundtrigger_middleware.PhraseSoundModel;
-import android.media.soundtrigger_middleware.RecognitionConfig;
-import android.media.soundtrigger_middleware.RecognitionEvent;
-import android.media.soundtrigger_middleware.SoundModel;
 import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
-import android.media.soundtrigger_middleware.Status;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ServiceSpecificException;
@@ -89,24 +89,12 @@
         return wrapper.attach(mDelegate.attach(handle, wrapper.getCallbackWrapper()));
     }
 
-    @Override
-    public void setCaptureState(boolean active) throws RemoteException {
-        // This is an internal call. No permissions needed.
-        mDelegate.setCaptureState(active);
-    }
-
     // Override toString() in order to have the delegate's ID in it.
     @Override
     public String toString() {
         return Objects.toString(mDelegate);
     }
 
-    @Override
-    public IBinder asBinder() {
-        throw new UnsupportedOperationException(
-                "This implementation is not inteded to be used directly with Binder.");
-    }
-
     /**
      * Get the identity context, or throws an InternalServerError if it has not been established.
      *
@@ -306,22 +294,28 @@
             }
 
             @Override
-            public void onRecognition(int modelHandle, RecognitionEvent event)
+            public void onRecognition(int modelHandle, RecognitionEvent event, int captureSession)
                     throws RemoteException {
                 enforcePermissions("Sound trigger recognition.");
-                mDelegate.onRecognition(modelHandle, event);
+                mDelegate.onRecognition(modelHandle, event, captureSession);
             }
 
             @Override
-            public void onPhraseRecognition(int modelHandle, PhraseRecognitionEvent event)
+            public void onPhraseRecognition(int modelHandle, PhraseRecognitionEvent event,
+                    int captureSession)
                     throws RemoteException {
                 enforcePermissions("Sound trigger phrase recognition.");
-                mDelegate.onPhraseRecognition(modelHandle, event);
+                mDelegate.onPhraseRecognition(modelHandle, event, captureSession);
             }
 
             @Override
-            public void onRecognitionAvailabilityChange(boolean available) throws RemoteException {
-                mDelegate.onRecognitionAvailabilityChange(available);
+            public void onResourcesAvailable() throws RemoteException {
+                mDelegate.onResourcesAvailable();
+            }
+
+            @Override
+            public void onModelUnloaded(int modelHandle) throws RemoteException {
+                mDelegate.onModelUnloaded(modelHandle);
             }
 
             @Override
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java
index db7a575..1995e54 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java
@@ -20,7 +20,10 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
-import android.hardware.soundtrigger.V2_0.ISoundTriggerHw;
+import android.media.soundtrigger.ModelParameterRange;
+import android.media.soundtrigger.PhraseSoundModel;
+import android.media.soundtrigger.RecognitionConfig;
+import android.media.soundtrigger.SoundModel;
 import android.media.permission.ClearCallingIdentityContext;
 import android.media.permission.Identity;
 import android.media.permission.PermissionUtil;
@@ -28,13 +31,8 @@
 import android.media.soundtrigger_middleware.ISoundTriggerCallback;
 import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
 import android.media.soundtrigger_middleware.ISoundTriggerModule;
-import android.media.soundtrigger_middleware.ModelParameterRange;
-import android.media.soundtrigger_middleware.PhraseSoundModel;
-import android.media.soundtrigger_middleware.RecognitionConfig;
-import android.media.soundtrigger_middleware.SoundModel;
 import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
 import android.os.RemoteException;
-import android.util.Log;
 
 import com.android.server.SystemService;
 
@@ -79,13 +77,6 @@
             @NonNull Context context) {
         mDelegate = Objects.requireNonNull(delegate);
         mContext = context;
-        new ExternalCaptureStateTracker(active -> {
-            try {
-                mDelegate.setCaptureState(active);
-            } catch (RemoteException e) {
-                throw e.rethrowAsRuntimeException();
-            }
-        });
     }
 
     @Override
@@ -232,23 +223,15 @@
 
         @Override
         public void onStart() {
-            HalFactory[] factories = new HalFactory[]{() -> {
-                try {
-                    Log.d(TAG, "Connecting to default ISoundTriggerHw");
-                    return ISoundTriggerHw.getService(true);
-                } catch (RemoteException e) {
-                    throw e.rethrowAsRuntimeException();
-                }
-            }};
+            HalFactory[] factories = new HalFactory[]{new DefaultHalFactory()};
 
             publishBinderService(Context.SOUND_TRIGGER_MIDDLEWARE_SERVICE,
-                    new SoundTriggerMiddlewareService(
-                            new SoundTriggerMiddlewareLogging(
-                                    new SoundTriggerMiddlewarePermission(
-                                            new SoundTriggerMiddlewareValidation(
-                                                    new SoundTriggerMiddlewareImpl(factories,
-                                                            new AudioSessionProviderImpl())),
-                                            getContext())), getContext()));
+                    new SoundTriggerMiddlewareService(new SoundTriggerMiddlewareLogging(
+                            new SoundTriggerMiddlewarePermission(
+                                    new SoundTriggerMiddlewareValidation(
+                                            new SoundTriggerMiddlewareImpl(factories,
+                                                    new AudioSessionProviderImpl())),
+                                    getContext())), getContext()));
         }
     }
 }
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java
index 95a30c7..d9fa792 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java
@@ -18,21 +18,21 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.media.soundtrigger.ModelParameterRange;
+import android.media.soundtrigger.PhraseRecognitionEvent;
+import android.media.soundtrigger.PhraseSoundModel;
+import android.media.soundtrigger.Properties;
+import android.media.soundtrigger.RecognitionConfig;
+import android.media.soundtrigger.RecognitionEvent;
+import android.media.soundtrigger.RecognitionStatus;
+import android.media.soundtrigger.SoundModel;
+import android.media.soundtrigger.Status;
 import android.media.permission.Identity;
 import android.media.permission.IdentityContext;
 import android.media.soundtrigger_middleware.ISoundTriggerCallback;
 import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
 import android.media.soundtrigger_middleware.ISoundTriggerModule;
-import android.media.soundtrigger_middleware.ModelParameterRange;
-import android.media.soundtrigger_middleware.PhraseRecognitionEvent;
-import android.media.soundtrigger_middleware.PhraseSoundModel;
-import android.media.soundtrigger_middleware.RecognitionConfig;
-import android.media.soundtrigger_middleware.RecognitionEvent;
-import android.media.soundtrigger_middleware.RecognitionStatus;
-import android.media.soundtrigger_middleware.SoundModel;
 import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
-import android.media.soundtrigger_middleware.SoundTriggerModuleProperties;
-import android.media.soundtrigger_middleware.Status;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ServiceSpecificException;
@@ -46,10 +46,6 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicReference;
 
 /**
  * This is a decorator of an {@link ISoundTriggerMiddlewareService}, which enforces correct usage by
@@ -89,7 +85,8 @@
  * }
  * </pre></code>
  * Following this patterns ensures a consistent and rigorous handling of all aspects associated
- * with client-server separation.
+ * with client-server separation. Notable exceptions are stopRecognition() and unloadModel(), which
+ * follow slightly more complicated rules for synchronization (see README.md for details).
  * <p>
  * <b>Exception handling approach:</b><br>
  * We make sure all client faults (argument and state validation) happen first, and
@@ -115,21 +112,18 @@
     }
 
     private class ModuleState {
-        final @NonNull SoundTriggerModuleProperties properties;
-        Set<Session> sessions = new HashSet<>();
+        public @NonNull Properties properties;
+        public Set<Session> sessions = new HashSet<>();
 
-        private ModuleState(@NonNull SoundTriggerModuleProperties properties) {
+        private ModuleState(@NonNull Properties properties) {
             this.properties = properties;
         }
     }
 
-    private AtomicReference<Boolean> mCaptureState = new AtomicReference<>();
-
     private final @NonNull ISoundTriggerMiddlewareInternal mDelegate;
     private Map<Integer, ModuleState> mModules;
 
-    public SoundTriggerMiddlewareValidation(
-            @NonNull ISoundTriggerMiddlewareInternal delegate) {
+    public SoundTriggerMiddlewareValidation(@NonNull ISoundTriggerMiddlewareInternal delegate) {
         mDelegate = delegate;
     }
 
@@ -137,8 +131,8 @@
      * Generic exception handling for exceptions thrown by the underlying implementation.
      *
      * Would throw any {@link RecoverableException} as a {@link ServiceSpecificException} (passed
-     * by Binder to the caller) and <i>any other</i> exception as {@link InternalServerError}
-     * (<b>not</b> passed by Binder to the caller).
+     * by Binder to the caller) and <i>any other</i> exception as a {@link ServiceSpecificException}
+     * with a {@link Status#INTERNAL_ERROR} code.
      * <p>
      * Typical usage:
      * <code><pre>
@@ -149,8 +143,7 @@
      * }
      * </pre></code>
      */
-    static @NonNull
-    RuntimeException handleException(@NonNull Exception e) {
+    static @NonNull RuntimeException handleException(@NonNull Exception e) {
         if (e instanceof RecoverableException) {
             throw new ServiceSpecificException(((RecoverableException) e).errorCode,
                     e.getMessage());
@@ -161,8 +154,7 @@
     }
 
     @Override
-    public @NonNull
-    SoundTriggerModuleDescriptor[] listModules() {
+    public @NonNull SoundTriggerModuleDescriptor[] listModules() {
         // Input validation (always valid).
 
         synchronized (this) {
@@ -186,6 +178,7 @@
                             throw new RuntimeException(
                                     "listModules must always return the same result.");
                         }
+                        mModules.get(desc.handle).properties = desc.properties;
                     }
                 }
                 return result;
@@ -223,23 +216,6 @@
         }
     }
 
-    @Override
-    public void setCaptureState(boolean active) {
-        // This is an internal call. No permissions needed.
-        //
-        // Normally, we would acquire a lock here. However, we do not access any state here so it
-        // is safe to not lock. This call is typically done from a different context than all the
-        // other calls and may result in a deadlock if we lock here (between the audio server and
-        // the system server).
-        try {
-            mDelegate.setCaptureState(active);
-        } catch (Exception e) {
-            throw handleException(e);
-        } finally {
-            mCaptureState.set(active);
-        }
-    }
-
     // Override toString() in order to have the delegate's ID in it.
     @Override
     public String toString() {
@@ -247,17 +223,8 @@
     }
 
     @Override
-    public IBinder asBinder() {
-        throw new UnsupportedOperationException(
-                "This implementation is not inteded to be used directly with Binder.");
-    }
-
-    @Override
     public void dump(PrintWriter pw) {
         synchronized (this) {
-            Boolean captureState = mCaptureState.get();
-            pw.printf("Capture state is %s\n\n", captureState == null ? "uninitialized"
-                    : (captureState ? "active" : "inactive"));
             if (mModules != null) {
                 for (int handle : mModules.keySet()) {
                     final ModuleState module = mModules.get(handle);
@@ -303,10 +270,14 @@
              * delivered to the caller (most commonly, for permission reasons).
              */
             INTERCEPTED,
+            /**
+             * Model has been preemptively unloaded by the HAL.
+             */
+            PREEMPTED,
         }
 
         /** Activity state. */
-        private AtomicInteger mActivityState = new AtomicInteger(Activity.LOADED.ordinal());
+        Activity activityState = Activity.LOADED;
 
         /** Human-readable description of the model. */
         final String description;
@@ -316,7 +287,7 @@
          * parameter is supported. A null value means it is known to not be supported. A non-null
          * value indicates the valid value range.
          */
-        private Map<Integer, ModelParameterRange> parameterSupport = new HashMap<>();
+        private final Map<Integer, ModelParameterRange> parameterSupport = new HashMap<>();
 
         /**
          * Check that the given parameter is known to be supported for this model.
@@ -351,24 +322,6 @@
             Preconditions.checkArgumentInRange(value, range.minInclusive, range.maxInclusive,
                     "value");
         }
-
-        /**
-         * Update support state for the given parameter for this model.
-         *
-         * @param modelParam The parameter key.
-         * @param range      The parameter value range, or null if not supported.
-         */
-        void updateParameterSupport(int modelParam, @Nullable ModelParameterRange range) {
-            parameterSupport.put(modelParam, range);
-        }
-
-        Activity getActivityState() {
-            return Activity.values()[mActivityState.get()];
-        }
-
-        void setActivityState(Activity activity) {
-            mActivityState.set(activity.ordinal());
-        }
     }
 
     /**
@@ -377,13 +330,7 @@
      */
     private class Session extends ISoundTriggerModule.Stub {
         private ISoundTriggerModule mDelegate;
-        // While generally all the fields of this class must be changed under a lock, an exception
-        // is made for the specific case of changing a model state from ACTIVE to LOADED, which
-        // may happen as result of a recognition callback. This would happen atomically and is
-        // necessary in order to avoid deadlocks associated with locking from within callbacks
-        // possibly originating from the audio server.
-        private @NonNull
-        ConcurrentMap<Integer, ModelState> mLoadedModels = new ConcurrentHashMap<>();
+        private final @NonNull Map<Integer, ModelState> mLoadedModels = new HashMap<>();
         private final int mHandle;
         private ModuleStatus mState = ModuleStatus.ALIVE;
         private final CallbackWrapper mCallbackWrapper;
@@ -451,7 +398,6 @@
         @Override
         public void unloadModel(int modelHandle) {
             // Input validation (always valid).
-
             synchronized (SoundTriggerMiddlewareValidation.this) {
                 // State validation.
                 if (mState == ModuleStatus.DETACHED) {
@@ -462,18 +408,24 @@
                 if (modelState == null) {
                     throw new IllegalStateException("Invalid handle: " + modelHandle);
                 }
-                if (modelState.getActivityState() != ModelState.Activity.LOADED) {
+                // To avoid race conditions, we treat LOADED and PREEMPTED exactly the same.
+                if (modelState.activityState != ModelState.Activity.LOADED
+                        && modelState.activityState != ModelState.Activity.PREEMPTED) {
                     throw new IllegalStateException("Model with handle: " + modelHandle
                             + " has invalid state for unloading");
                 }
+            }
 
-                // From here on, every exception isn't client's fault.
-                try {
-                    mDelegate.unloadModel(modelHandle);
-                    mLoadedModels.remove(modelHandle);
-                } catch (Exception e) {
-                    throw handleException(e);
-                }
+            // From here on, every exception isn't client's fault.
+            try {
+                // Calling the delegate must be done outside the lock.
+                mDelegate.unloadModel(modelHandle);
+            } catch (Exception e) {
+                throw handleException(e);
+            }
+
+            synchronized (SoundTriggerMiddlewareValidation.this) {
+                mLoadedModels.remove(modelHandle);
             }
         }
 
@@ -492,20 +444,19 @@
                 if (modelState == null) {
                     throw new IllegalStateException("Invalid handle: " + modelHandle);
                 }
-                if (modelState.getActivityState() != ModelState.Activity.LOADED) {
+                ModelState.Activity activityState = modelState.activityState;
+                // To avoid race conditions, we treat LOADED and PREEMPTED exactly the same.
+                if (activityState != ModelState.Activity.LOADED
+                        && activityState != ModelState.Activity.PREEMPTED) {
                     throw new IllegalStateException("Model with handle: " + modelHandle
                             + " has invalid state for starting recognition");
                 }
 
                 // From here on, every exception isn't client's fault.
                 try {
-                    // Normally, we would set the state after the operation succeeds. However, since
-                    // the activity state may be reset outside of the lock, we set it here first,
-                    // and reset it in case of exception.
-                    modelState.setActivityState(ModelState.Activity.ACTIVE);
                     mDelegate.startRecognition(modelHandle, config);
+                    modelState.activityState = ModelState.Activity.ACTIVE;
                 } catch (Exception e) {
-                    modelState.setActivityState(ModelState.Activity.LOADED);
                     throw handleException(e);
                 }
             }
@@ -529,17 +480,36 @@
 
                 // From here on, every exception isn't client's fault.
                 try {
-                    // If the activity state is LOADED or INTERCEPTED, we skip delegating the
-                    // command, but still consider the call valid. In either case, the resulting
-                    // state is LOADED.
-                    if (modelState.getActivityState() == ModelState.Activity.ACTIVE) {
-                        mDelegate.stopRecognition(modelHandle);
+                    // If the activity state is INTERCEPTED, we skip delegating the command, but
+                    // still consider the call valid.
+                    if (modelState.activityState == ModelState.Activity.INTERCEPTED) {
+                        modelState.activityState = ModelState.Activity.LOADED;
+                        return;
                     }
-                    modelState.setActivityState(ModelState.Activity.LOADED);
                 } catch (Exception e) {
                     throw handleException(e);
                 }
             }
+
+            // Calling the delegate's stop must be done without the lock.
+            try {
+                mDelegate.stopRecognition(modelHandle);
+            } catch (Exception e) {
+                throw handleException(e);
+            }
+
+            synchronized (SoundTriggerMiddlewareValidation.this) {
+                ModelState modelState = mLoadedModels.get(modelHandle);
+                if (modelState == null) {
+                    // The model was unloaded while we let go of the lock.
+                    return;
+                }
+
+                // After the call, the state is LOADED, unless it has been first preempted.
+                if (modelState.activityState != ModelState.Activity.PREEMPTED) {
+                    modelState.activityState = ModelState.Activity.LOADED;
+                }
+            }
         }
 
         @Override
@@ -562,7 +532,7 @@
                 try {
                     // If the activity state is LOADED or INTERCEPTED, we skip delegating the
                     // command, but still consider the call valid.
-                    if (modelState.getActivityState() == ModelState.Activity.ACTIVE) {
+                    if (modelState.activityState == ModelState.Activity.ACTIVE) {
                         mDelegate.forceRecognitionEvent(modelHandle);
                     }
                 } catch (Exception e) {
@@ -644,7 +614,7 @@
                 try {
                     ModelParameterRange result = mDelegate.queryModelParameterSupport(modelHandle,
                             modelParam);
-                    modelState.updateParameterSupport(modelParam, result);
+                    modelState.parameterSupport.put(modelParam, result);
                     return result;
                 } catch (Exception e) {
                     throw handleException(e);
@@ -702,7 +672,7 @@
                 for (Map.Entry<Integer, ModelState> entry : mLoadedModels.entrySet()) {
                     pw.print(entry.getKey());
                     pw.print('\t');
-                    pw.print(entry.getValue().getActivityState().name());
+                    pw.print(entry.getValue().activityState.name());
                     pw.print('\t');
                     pw.print(entry.getValue().description);
                     pw.println();
@@ -732,70 +702,79 @@
             }
 
             @Override
-        public void onRecognition(int modelHandle, @NonNull RecognitionEvent event) {
-            // We cannot obtain a lock on SoundTriggerMiddlewareValidation.this, since this call
-            // might be coming from the audio server (via setCaptureState()) while it is holding
-            // a lock that is also acquired while loading / unloading models. Thus, we require a
-            // strict locking order here, where obtaining our lock must always come first.
-            // To avoid this problem, we use an atomic model activity state. There is a risk of the
-            // model not being in the mLoadedModels map here, since it might have been stopped /
-            // unloaded while the event was in flight.
-            ModelState modelState = mLoadedModels.get(modelHandle);
-            if (modelState != null) {
-                if (event.status != RecognitionStatus.FORCED) {
-                    modelState.setActivityState(ModelState.Activity.LOADED);
-                }
-                try {
-                    mCallback.onRecognition(modelHandle, event);
-                } catch (Exception e) {
-                    // Dead client will be handled by binderDied() - no need to handle here.
-                    // In any case, client callbacks are considered best effort.
-                    Log.e(TAG, "Client callback exception.", e);
+            public void onRecognition(int modelHandle, @NonNull RecognitionEvent event,
+                    int captureSession) {
+                synchronized (SoundTriggerMiddlewareValidation.this) {
+                    ModelState modelState = mLoadedModels.get(modelHandle);
                     if (event.status != RecognitionStatus.FORCED) {
-                        modelState.setActivityState(ModelState.Activity.INTERCEPTED);
+                        modelState.activityState = ModelState.Activity.LOADED;
+                    }
+                }
+
+                // Calling the delegate callback must be done outside the lock.
+                try {
+                    mCallback.onRecognition(modelHandle, event, captureSession);
+                } catch (Exception e) {
+                    Log.w(TAG, "Client callback exception.", e);
+                    synchronized (SoundTriggerMiddlewareValidation.this) {
+                        ModelState modelState = mLoadedModels.get(modelHandle);
+                        if (event.status != RecognitionStatus.FORCED) {
+                            modelState.activityState = ModelState.Activity.INTERCEPTED;
+                        }
                     }
                 }
             }
-        }
 
-        @Override
-        public void onPhraseRecognition(int modelHandle, @NonNull PhraseRecognitionEvent event) {
-            // We cannot obtain a lock on SoundTriggerMiddlewareValidation.this, since this call
-            // might be coming from the audio server (via setCaptureState()) while it is holding
-            // a lock that is also acquired while loading / unloading models. Thus, we require a
-            // strict locking order here, where obtaining our lock must always come first.
-            // To avoid this problem, we use an atomic model activity state. There is a risk of the
-            // model not being in the mLoadedModels map here, since it might have been stopped /
-            // unloaded while the event was in flight.
-            ModelState modelState = mLoadedModels.get(modelHandle);
-            if (modelState != null) {
-                if (event.common.status != RecognitionStatus.FORCED) {
-                        modelState.setActivityState(ModelState.Activity.LOADED);
+            @Override
+            public void onPhraseRecognition(int modelHandle,
+                    @NonNull PhraseRecognitionEvent event, int captureSession) {
+                synchronized (SoundTriggerMiddlewareValidation.this) {
+                    ModelState modelState = mLoadedModels.get(modelHandle);
+                    if (event.common.status != RecognitionStatus.FORCED) {
+                        modelState.activityState = ModelState.Activity.LOADED;
+                    }
                 }
+
+                // Calling the delegate callback must be done outside the lock.
                 try {
-                    mCallback.onPhraseRecognition(modelHandle, event);
+                    mCallback.onPhraseRecognition(modelHandle, event, captureSession);
                 } catch (Exception e) {
+                    Log.w(TAG, "Client callback exception.", e);
+                    synchronized (SoundTriggerMiddlewareValidation.this) {
+                        ModelState modelState = mLoadedModels.get(modelHandle);
+                        if (event.common.status != RecognitionStatus.FORCED) {
+                            modelState.activityState = ModelState.Activity.INTERCEPTED;
+                        }
+                    }
+                }
+            }
+
+            @Override
+            public void onModelUnloaded(int modelHandle) {
+                synchronized (SoundTriggerMiddlewareValidation.this) {
+                    ModelState modelState = mLoadedModels.get(modelHandle);
+                    modelState.activityState = ModelState.Activity.PREEMPTED;
+                }
+
+                // Calling the delegate callback must be done outside the lock.
+                try {
+                    mCallback.onModelUnloaded(modelHandle);
+                } catch (Exception e) {
+                    Log.w(TAG, "Client callback exception.", e);
+                }
+            }
+
+            @Override
+            public void onResourcesAvailable() {
+                // Not locking to avoid deadlocks (not affecting any state).
+                try {
+                    mCallback.onResourcesAvailable();
+                } catch (RemoteException e) {
                     // Dead client will be handled by binderDied() - no need to handle here.
                     // In any case, client callbacks are considered best effort.
                     Log.e(TAG, "Client callback exception.", e);
-                    if (event.common.status != RecognitionStatus.FORCED) {
-                        modelState.setActivityState(ModelState.Activity.INTERCEPTED);
-                    }
                 }
             }
-        }
-
-        @Override
-        public void onRecognitionAvailabilityChange(boolean available) {
-            // Not locking to avoid deadlocks (not affecting any state).
-            try {
-                mCallback.onRecognitionAvailabilityChange(available);
-            } catch (RemoteException e) {
-                // Dead client will be handled by binderDied() - no need to handle here.
-                // In any case, client callbacks are considered best effort.
-                Log.e(TAG, "Client callback exception.", e);
-            }
-        }
 
             @Override
             public void onModuleDied() {
@@ -820,8 +799,7 @@
                         // Gracefully stop all active recognitions and unload the models.
                         for (Map.Entry<Integer, ModelState> entry :
                                 mLoadedModels.entrySet()) {
-                            if (entry.getValue().getActivityState()
-                                    == ModelState.Activity.ACTIVE) {
+                            if (entry.getValue().activityState == ModelState.Activity.ACTIVE) {
                                 mDelegate.stopRecognition(entry.getKey());
                             }
                             mDelegate.unloadModel(entry.getKey());
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
index 02d978d..f211158 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
@@ -18,30 +18,24 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback;
-import android.hardware.soundtrigger.V2_2.ISoundTriggerHw;
+import android.media.soundtrigger.ModelParameterRange;
+import android.media.soundtrigger.PhraseRecognitionEvent;
+import android.media.soundtrigger.PhraseSoundModel;
+import android.media.soundtrigger.Properties;
+import android.media.soundtrigger.RecognitionConfig;
+import android.media.soundtrigger.RecognitionEvent;
+import android.media.soundtrigger.RecognitionStatus;
+import android.media.soundtrigger.SoundModel;
+import android.media.soundtrigger.Status;
 import android.media.soundtrigger_middleware.ISoundTriggerCallback;
 import android.media.soundtrigger_middleware.ISoundTriggerModule;
-import android.media.soundtrigger_middleware.ModelParameterRange;
-import android.media.soundtrigger_middleware.PhraseRecognitionEvent;
-import android.media.soundtrigger_middleware.PhraseRecognitionExtra;
-import android.media.soundtrigger_middleware.PhraseSoundModel;
-import android.media.soundtrigger_middleware.RecognitionConfig;
-import android.media.soundtrigger_middleware.RecognitionEvent;
-import android.media.soundtrigger_middleware.RecognitionStatus;
-import android.media.soundtrigger_middleware.SoundModel;
-import android.media.soundtrigger_middleware.SoundModelType;
-import android.media.soundtrigger_middleware.SoundTriggerModuleProperties;
-import android.media.soundtrigger_middleware.Status;
 import android.os.IBinder;
-import android.os.IHwBinder;
 import android.os.RemoteException;
 import android.util.Log;
 
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -71,9 +65,8 @@
  * <li>There is no binder instance associated with this implementation. Do not call asBinder().
  * <li>The implementation may throw a {@link RecoverableException} to indicate non-fatal,
  * recoverable faults. The error code would one of the
- * {@link android.media.soundtrigger_middleware.Status} constants. Any other exception
- * thrown should be regarded as a bug in the implementation or one of its dependencies
- * (assuming correct usage).
+ * {@link android.media.soundtrigger.Status} constants. Any other exception thrown should be
+ * regarded as a bug in the implementation or one of its dependencies (assuming correct usage).
  * <li>The implementation is designed for testability by featuring dependency injection (the
  * underlying HAL driver instances are passed to the ctor) and by minimizing dependencies
  * on Android runtime.
@@ -88,15 +81,13 @@
  *
  * @hide
  */
-class SoundTriggerModule implements IHwBinder.DeathRecipient {
+class SoundTriggerModule implements IBinder.DeathRecipient, ISoundTriggerHal.GlobalCallback {
     static private final String TAG = "SoundTriggerModule";
-    @NonNull private HalFactory mHalFactory;
-    @NonNull private ISoundTriggerHw2 mHalService;
+    @NonNull private final HalFactory mHalFactory;
+    @NonNull private ISoundTriggerHal mHalService;
     @NonNull private final SoundTriggerMiddlewareImpl.AudioSessionProvider mAudioSessionProvider;
     private final Set<Session> mActiveSessions = new HashSet<>();
-    private int mNumLoadedModels = 0;
-    private SoundTriggerModuleProperties mProperties;
-    private boolean mRecognitionAvailable;
+    private Properties mProperties;
 
     /**
      * Ctor.
@@ -110,9 +101,6 @@
         mAudioSessionProvider = audioSessionProvider;
 
         attachToHal();
-        mProperties = ConversionUtil.hidl2aidlProperties(mHalService.getProperties());
-        // We conservatively assume that external capture is active until explicitly told otherwise.
-        mRecognitionAvailable = mProperties.concurrentCapture;
     }
 
     /**
@@ -140,50 +128,13 @@
      * @return The properties structure.
      */
     synchronized @NonNull
-    SoundTriggerModuleProperties getProperties() {
+    Properties getProperties() {
         return mProperties;
     }
 
-    /**
-     * Notify the module that external capture has started / finished, using the same input device
-     * used for recognition.
-     * If the underlying driver does not support recognition while capturing, capture will be
-     * aborted, and the recognition callback will receive and abort event. In addition, all active
-     * clients will be notified of the change in state.
-     *
-     * @param active true iff external capture is active.
-     */
-    void setExternalCaptureState(boolean active) {
-        // We should never invoke callbacks while holding the lock, since this may deadlock with
-        // forward calls. Thus, we first gather all the callbacks we need to invoke while holding
-        // the lock, but invoke them after releasing it.
-        List<Runnable> callbacks = new LinkedList<>();
-
-        synchronized (this) {
-            if (mProperties.concurrentCapture) {
-                // If we support concurrent capture, we don't care about any of this.
-                return;
-            }
-            mRecognitionAvailable = !active;
-            if (!mRecognitionAvailable) {
-                // Our module does not support recognition while a capture is active -
-                // need to abort all active recognitions.
-                for (Session session : mActiveSessions) {
-                    session.abortActiveRecognitions(callbacks);
-                }
-            }
-        }
-        for (Runnable callback : callbacks) {
-            callback.run();
-        }
-        for (Session session : mActiveSessions) {
-            session.notifyRecognitionAvailability();
-        }
-    }
-
     @Override
-    public void serviceDied(long cookie) {
-        Log.w(TAG, String.format("Underlying HAL driver died."));
+    public void binderDied() {
+        Log.w(TAG, "Underlying HAL driver died.");
         List<ISoundTriggerCallback> callbacks;
         synchronized (this) {
             callbacks = new ArrayList<>(mActiveSessions.size());
@@ -207,20 +158,19 @@
      * Resets the transient state of this object.
      */
     private void reset() {
+        mHalService.detach();
         attachToHal();
-        // We conservatively assume that external capture is active until explicitly told otherwise.
-        mRecognitionAvailable = mProperties.concurrentCapture;
-        mNumLoadedModels = 0;
     }
 
     /**
      * Attached to the HAL service via factory.
      */
     private void attachToHal() {
-        mHalService = new SoundTriggerHw2Enforcer(
-                new SoundTriggerHw2Watchdog(
-                        new SoundTriggerHw2Compat(mHalFactory.create())));
-        mHalService.linkToDeath(this, 0);
+        mHalService = new SoundTriggerHalEnforcer(
+                new SoundTriggerHalWatchdog(mHalFactory.create()));
+        mHalService.linkToDeath(this);
+        mHalService.registerCallback(this);
+        mProperties = mHalService.getProperties();
     }
 
     /**
@@ -232,6 +182,25 @@
         mActiveSessions.remove(session);
     }
 
+    @Override
+    public void onResourcesAvailable() {
+        List<ISoundTriggerCallback> callbacks;
+        synchronized (this) {
+            callbacks = new ArrayList<>(mActiveSessions.size());
+            for (Session session : mActiveSessions) {
+                callbacks.add(session.mCallback);
+            }
+        }
+        // Trigger the callbacks outside of the lock to avoid deadlocks.
+        for (ISoundTriggerCallback callback : callbacks) {
+            try {
+                callback.onResourcesAvailable();
+            } catch (RemoteException e) {
+                throw e.rethrowAsRuntimeException();
+            }
+        }
+    }
+
     /** State of a single sound model. */
     private enum ModelState {
         /** Initial state, until load() is called. */
@@ -249,7 +218,7 @@
      */
     private class Session implements ISoundTriggerModule {
         private ISoundTriggerCallback mCallback;
-        private Map<Integer, Model> mLoadedModels = new HashMap<>();
+        private final Map<Integer, Model> mLoadedModels = new HashMap<>();
 
         /**
          * Ctor.
@@ -258,7 +227,6 @@
          */
         private Session(@NonNull ISoundTriggerCallback callback) {
             mCallback = callback;
-            notifyRecognitionAvailability();
         }
 
         @Override
@@ -274,95 +242,65 @@
 
         @Override
         public int loadModel(@NonNull SoundModel model) {
-            // We must do this outside the lock, to avoid possible deadlocks with the remote process
-            // that provides the audio sessions, which may also be calling into us.
-            SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession audioSession =
-                    mAudioSessionProvider.acquireSession();
-
-            try {
-                synchronized (SoundTriggerModule.this) {
-                    checkValid();
-                    if (mNumLoadedModels == mProperties.maxSoundModels) {
-                        throw new RecoverableException(Status.RESOURCE_CONTENTION,
-                                "Maximum number of models loaded.");
-                    }
-                    Model loadedModel = new Model();
-                    int result = loadedModel.load(model, audioSession);
-                    ++mNumLoadedModels;
-                    return result;
-                }
-            } catch (Exception e) {
-                // We must do this outside the lock, to avoid possible deadlocks with the remote
-                // process that provides the audio sessions, which may also be calling into us.
+            synchronized (SoundTriggerModule.this) {
+                SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession audioSession =
+                        mAudioSessionProvider.acquireSession();
                 try {
-                    mAudioSessionProvider.releaseSession(audioSession.mSessionHandle);
-                } catch (Exception ee) {
-                    Log.e(TAG, "Failed to release session.", ee);
+                    checkValid();
+                    Model loadedModel = new Model();
+                    return loadedModel.load(model, audioSession);
+                } catch (Exception e) {
+                    // We must do this outside the lock, to avoid possible deadlocks with the remote
+                    // process that provides the audio sessions, which may also be calling into us.
+                    try {
+                        mAudioSessionProvider.releaseSession(audioSession.mSessionHandle);
+                    } catch (Exception ee) {
+                        Log.e(TAG, "Failed to release session.", ee);
+                    }
+                    throw e;
                 }
-                throw e;
             }
         }
 
         @Override
         public int loadPhraseModel(@NonNull PhraseSoundModel model) {
-            // We must do this outside the lock, to avoid possible deadlocks with the remote process
-            // that provides the audio sessions, which may also be calling into us.
-            SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession audioSession =
-                    mAudioSessionProvider.acquireSession();
-
-            try {
-                synchronized (SoundTriggerModule.this) {
+            synchronized (SoundTriggerModule.this) {
+                SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession audioSession =
+                        mAudioSessionProvider.acquireSession();
+                try {
                     checkValid();
-                    if (mNumLoadedModels == mProperties.maxSoundModels) {
-                        throw new RecoverableException(Status.RESOURCE_CONTENTION,
-                                "Maximum number of models loaded.");
-                    }
                     Model loadedModel = new Model();
                     int result = loadedModel.load(model, audioSession);
-                    ++mNumLoadedModels;
                     Log.d(TAG, String.format("loadPhraseModel()->%d", result));
                     return result;
+                } catch (Exception e) {
+                    // We must do this outside the lock, to avoid possible deadlocks with the remote
+                    // process that provides the audio sessions, which may also be calling into us.
+                    try {
+                        mAudioSessionProvider.releaseSession(audioSession.mSessionHandle);
+                    } catch (Exception ee) {
+                        Log.e(TAG, "Failed to release session.", ee);
+                    }
+                    throw e;
                 }
-            } catch (Exception e) {
-                // We must do this outside the lock, to avoid possible deadlocks with the remote
-                // process that provides the audio sessions, which may also be calling into us.
-                try {
-                    mAudioSessionProvider.releaseSession(audioSession.mSessionHandle);
-                } catch (Exception ee) {
-                    Log.e(TAG, "Failed to release session.", ee);
-                }
-                throw e;
             }
         }
 
         @Override
         public void unloadModel(int modelHandle) {
-            int sessionId;
             synchronized (SoundTriggerModule.this) {
+                int sessionId;
                 checkValid();
                 sessionId = mLoadedModels.get(modelHandle).unload();
-                --mNumLoadedModels;
+                mAudioSessionProvider.releaseSession(sessionId);
             }
-
-            // We must do this outside the lock, to avoid possible deadlocks with the remote process
-            // that provides the audio sessions, which may also be calling into us.
-            mAudioSessionProvider.releaseSession(sessionId);
         }
 
         @Override
         public void startRecognition(int modelHandle, @NonNull RecognitionConfig config) {
-            // We should never invoke callbacks while holding the lock, since this may deadlock with
-            // forward calls. Thus, we first gather all the callbacks we need to invoke while holding
-            // the lock, but invoke them after releasing it.
-            List<Runnable> callbacks = new LinkedList<>();
-
             synchronized (SoundTriggerModule.this) {
                 checkValid();
-                mLoadedModels.get(modelHandle).startRecognition(config, callbacks);
-            }
-
-            for (Runnable callback : callbacks) {
-                callback.run();
+                mLoadedModels.get(modelHandle).startRecognition(config);
             }
         }
 
@@ -407,27 +345,6 @@
         }
 
         /**
-         * Abort all currently active recognitions.
-         * @param callbacks Will be appended with a list of callbacks that need to be invoked
-         *                  after this method returns, without holding the module lock.
-         */
-        private void abortActiveRecognitions(@NonNull List<Runnable> callbacks) {
-            for (Model model : mLoadedModels.values()) {
-                model.abortActiveRecognition(callbacks);
-            }
-        }
-
-        private void notifyRecognitionAvailability() {
-            try {
-                mCallback.onRecognitionAvailabilityChange(mRecognitionAvailable);
-            } catch (RemoteException e) {
-                // Dead client will be handled by binderDied() - no need to handle here.
-                // In any case, client callbacks are considered best effort.
-                Log.e(TAG, "Client callback execption.", e);
-            }
-        }
-
-        /**
          * The underlying module HAL is dead.
          * @return The client callback that needs to be invoked to notify the client.
          */
@@ -455,10 +372,9 @@
          *
          * All model-based operations are delegated to this class and implemented here.
          */
-        private class Model implements ISoundTriggerHw2.Callback {
+        private class Model implements ISoundTriggerHal.ModelCallback {
             public int mHandle;
             private ModelState mState = ModelState.INIT;
-            private int mModelType = SoundModelType.UNKNOWN;
             private SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession mSession;
 
             private @NonNull
@@ -473,11 +389,8 @@
 
             private int load(@NonNull SoundModel model,
                     SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession audioSession) {
-                mModelType = model.type;
                 mSession = audioSession;
-                ISoundTriggerHw.SoundModel hidlModel = ConversionUtil.aidl2hidlSoundModel(model);
-
-                mHandle = mHalService.loadSoundModel(hidlModel, this, 0);
+                mHandle = mHalService.loadSoundModel(model, this);
                 setState(ModelState.LOADED);
                 mLoadedModels.put(mHandle, this);
                 return mHandle;
@@ -485,12 +398,8 @@
 
             private int load(@NonNull PhraseSoundModel model,
                     SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession audioSession) {
-                mModelType = model.common.type;
                 mSession = audioSession;
-                ISoundTriggerHw.PhraseSoundModel hidlModel =
-                        ConversionUtil.aidl2hidlPhraseSoundModel(model);
-
-                mHandle = mHalService.loadPhraseSoundModel(hidlModel, this, 0);
+                mHandle = mHalService.loadPhraseSoundModel(model, this);
 
                 setState(ModelState.LOADED);
                 mLoadedModels.put(mHandle, this);
@@ -507,18 +416,9 @@
                 return mSession.mSessionHandle;
             }
 
-            private void startRecognition(@NonNull RecognitionConfig config,
-                    @NonNull List<Runnable> callbacks) {
-                if (!mRecognitionAvailable) {
-                    // Recognition is unavailable - send an abort event immediately.
-                    callbacks.add(this::notifyAbort);
-                    return;
-                }
-                android.hardware.soundtrigger.V2_3.RecognitionConfig hidlConfig =
-                        ConversionUtil.aidl2hidlRecognitionConfig(config);
-                hidlConfig.base.header.captureDevice = mSession.mDeviceHandle;
-                hidlConfig.base.header.captureHandle = mSession.mIoHandle;
-                mHalService.startRecognition(mHandle, hidlConfig, this, 0);
+            private void startRecognition(@NonNull RecognitionConfig config) {
+                mHalService.startRecognition(mHandle, mSession.mDeviceHandle,
+                        mSession.mIoHandle, config);
                 setState(ModelState.ACTIVE);
             }
 
@@ -537,7 +437,7 @@
                     // This call is idempotent in order to avoid races.
                     return;
                 }
-                mHalService.getModelState(mHandle);
+                mHalService.forceRecognitionEvent(mHandle);
             }
 
 
@@ -553,78 +453,24 @@
 
             @Nullable
             private ModelParameterRange queryModelParameterSupport(int modelParam) {
-                return ConversionUtil.hidl2aidlModelParameterRange(
-                        mHalService.queryParameter(mHandle,
-                                ConversionUtil.aidl2hidlModelParameter(modelParam)));
-            }
-
-            /**
-             * Abort the recognition, if active.
-             * @param callbacks Will be appended with a list of callbacks that need to be invoked
-             *                  after this method returns, without holding the module lock.
-             */
-            private void abortActiveRecognition(List<Runnable> callbacks) {
-                // If we're inactive, do nothing.
-                if (getState() != ModelState.ACTIVE) {
-                    return;
-                }
-                // Stop recognition.
-                stopRecognition();
-
-                // Notify the client that recognition has been aborted.
-                callbacks.add(this::notifyAbort);
-            }
-
-            /** Notify the client that recognition has been aborted. */
-            private void notifyAbort() {
-                try {
-                    switch (mModelType) {
-                        case SoundModelType.GENERIC: {
-                            android.media.soundtrigger_middleware.RecognitionEvent event =
-                                    newEmptyRecognitionEvent();
-                            event.status =
-                                    android.media.soundtrigger_middleware.RecognitionStatus.ABORTED;
-                            event.type = SoundModelType.GENERIC;
-                            mCallback.onRecognition(mHandle, event);
-                        }
-                        break;
-
-                        case SoundModelType.KEYPHRASE: {
-                            android.media.soundtrigger_middleware.PhraseRecognitionEvent event =
-                                    newEmptyPhraseRecognitionEvent();
-                            event.common.status =
-                                    android.media.soundtrigger_middleware.RecognitionStatus.ABORTED;
-                            event.common.type = SoundModelType.KEYPHRASE;
-                            mCallback.onPhraseRecognition(mHandle, event);
-                        }
-                        break;
-
-                        default:
-                            Log.e(TAG, "Unknown model type: " + mModelType);
-
-                    }
-                } catch (RemoteException e) {
-                    // Dead client will be handled by binderDied() - no need to handle here.
-                    // In any case, client callbacks are considered best effort.
-                    Log.e(TAG, "Client callback execption.", e);
-                }
+                return mHalService.queryParameter(mHandle, modelParam);
             }
 
             @Override
-            public void recognitionCallback(
-                    @NonNull ISoundTriggerHwCallback.RecognitionEvent recognitionEvent,
-                    int cookie) {
-                RecognitionEvent aidlEvent =
-                        ConversionUtil.hidl2aidlRecognitionEvent(recognitionEvent);
-                aidlEvent.captureSession = mSession.mSessionHandle;
+            public void recognitionCallback(int modelHandle,
+                    @NonNull RecognitionEvent recognitionEvent) {
+                ISoundTriggerCallback callback;
                 synchronized (SoundTriggerModule.this) {
-                    if (aidlEvent.status != RecognitionStatus.FORCED) {
+                    if (recognitionEvent.status != RecognitionStatus.FORCED) {
                         setState(ModelState.LOADED);
                     }
+                    callback = mCallback;
                 }
                 // The callback must be invoked outside of the lock.
                 try {
-                    mCallback.onRecognition(mHandle, aidlEvent);
+                    if (callback != null) {
+                        callback.onRecognition(mHandle, recognitionEvent, mSession.mSessionHandle);
+                    }
                 } catch (RemoteException e) {
                     // We're not expecting any exceptions here.
                     throw e.rethrowAsRuntimeException();
@@ -632,22 +478,40 @@
             }
 
             @Override
-            public void phraseRecognitionCallback(
-                    @NonNull ISoundTriggerHwCallback.PhraseRecognitionEvent phraseRecognitionEvent,
-                    int cookie) {
-                PhraseRecognitionEvent aidlEvent =
-                        ConversionUtil.hidl2aidlPhraseRecognitionEvent(phraseRecognitionEvent);
-                aidlEvent.common.captureSession = mSession.mSessionHandle;
-
+            public void phraseRecognitionCallback(int modelHandle,
+                    @NonNull PhraseRecognitionEvent phraseRecognitionEvent) {
+                ISoundTriggerCallback callback;
                 synchronized (SoundTriggerModule.this) {
-                    if (aidlEvent.common.status != RecognitionStatus.FORCED) {
+                    if (phraseRecognitionEvent.common.status != RecognitionStatus.FORCED) {
                         setState(ModelState.LOADED);
                     }
+                    callback = mCallback;
                 }
 
                 // The callback must be invoked outside of the lock.
                 try {
-                    mCallback.onPhraseRecognition(mHandle, aidlEvent);
+                    if (callback != null) {
+                        mCallback.onPhraseRecognition(mHandle, phraseRecognitionEvent,
+                                mSession.mSessionHandle);
+                    }
+                } catch (RemoteException e) {
+                    // We're not expecting any exceptions here.
+                    throw e.rethrowAsRuntimeException();
+                }
+            }
+
+            @Override
+            public void modelUnloaded(int modelHandle) {
+                ISoundTriggerCallback callback;
+                synchronized (SoundTriggerModule.this) {
+                    callback = mCallback;
+                }
+
+                // The callback must be invoked outside of the lock.
+                try {
+                    if (callback != null) {
+                        callback.onModelUnloaded(modelHandle);
+                    }
                 } catch (RemoteException e) {
                     // We're not expecting any exceptions here.
                     throw e.rethrowAsRuntimeException();
@@ -655,33 +519,4 @@
             }
         }
     }
-
-    /**
-     * Creates a default-initialized recognition event.
-     *
-     * Non-nullable object fields are default constructed.
-     * Non-nullable array fields are initialized to 0 length.
-     *
-     * @return The event.
-     */
-    private static RecognitionEvent newEmptyRecognitionEvent() {
-        RecognitionEvent result = new RecognitionEvent();
-        result.data = new byte[0];
-        return result;
-    }
-
-    /**
-     * Creates a default-initialized phrase recognition event.
-     *
-     * Non-nullable object fields are default constructed.
-     * Non-nullable array fields are initialized to 0 length.
-     *
-     * @return The event.
-     */
-    private static PhraseRecognitionEvent newEmptyPhraseRecognitionEvent() {
-        PhraseRecognitionEvent result = new PhraseRecognitionEvent();
-        result.common = newEmptyRecognitionEvent();
-        result.phraseExtras = new PhraseRecognitionExtra[0];
-        return result;
-    }
 }
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/ValidationUtil.java b/services/core/java/com/android/server/soundtrigger_middleware/ValidationUtil.java
index e05c468..4d52121 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/ValidationUtil.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/ValidationUtil.java
@@ -17,21 +17,18 @@
 package com.android.server.soundtrigger_middleware;
 
 import android.annotation.Nullable;
-import android.media.soundtrigger_middleware.ConfidenceLevel;
-import android.media.soundtrigger_middleware.ModelParameter;
-import android.media.soundtrigger_middleware.Phrase;
-import android.media.soundtrigger_middleware.PhraseRecognitionExtra;
-import android.media.soundtrigger_middleware.PhraseSoundModel;
-import android.media.soundtrigger_middleware.RecognitionConfig;
-import android.media.soundtrigger_middleware.RecognitionMode;
-import android.media.soundtrigger_middleware.SoundModel;
-import android.media.soundtrigger_middleware.SoundModelType;
-
-import com.android.internal.util.Preconditions;
+import android.media.soundtrigger.ConfidenceLevel;
+import android.media.soundtrigger.ModelParameter;
+import android.media.soundtrigger.Phrase;
+import android.media.soundtrigger.PhraseRecognitionExtra;
+import android.media.soundtrigger.PhraseSoundModel;
+import android.media.soundtrigger.RecognitionConfig;
+import android.media.soundtrigger.RecognitionMode;
+import android.media.soundtrigger.SoundModel;
+import android.media.soundtrigger.SoundModelType;
 
 import java.util.Objects;
 import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 
 /**
  * Utilities for asserting the validity of various data types used by this module.
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java b/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java
index 5723e1d..ee30fa2 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java
@@ -50,8 +50,6 @@
      */
     private final int mProcessId;
 
-    private boolean mIsForeground;
-
     /**
      * All the clients that share the same resource would be under the same group id.
      *
@@ -90,6 +88,12 @@
     private int mUsingCiCamId = INVALID_RESOURCE_ID;
 
     /**
+     * If the priority is overwritten through
+     * {@link TunerResourceManagerService#setPriority(int, int)}.
+     */
+    private boolean mIsPriorityOverwritten = false;
+
+    /**
      * Optional arbitrary priority value given by the client.
      *
      * <p>This value can override the default priorotiy calculated from
@@ -121,17 +125,10 @@
     }
 
     /**
-     * Set the current isForeground status.
+     * If the client priority is overwrttien.
      */
-    public void setForeground(boolean isForeground) {
-        mIsForeground = isForeground;
-    }
-
-    /**
-     * Get the previous recorded isForeground status.
-     */
-    public boolean isForeground() {
-        return mIsForeground;
+    public boolean isPriorityOverwritten() {
+        return mIsPriorityOverwritten;
     }
 
     public int getGroupId() {
@@ -153,6 +150,17 @@
         mPriority = priority;
     }
 
+    /**
+     * Overwrite the client priority.
+     */
+    public void overwritePriority(int priority) {
+        if (priority < 0) {
+            return;
+        }
+        mIsPriorityOverwritten = true;
+        mPriority = priority;
+    }
+
     public void setNiceValue(int niceValue) {
         mNiceValue = niceValue;
     }
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
index 988582d..0c04b07 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
@@ -507,9 +507,8 @@
                                               .useCase(profile.useCase)
                                               .processId(pid)
                                               .build();
-        clientProfile.setForeground(checkIsForeground(pid));
         clientProfile.setPriority(
-                getClientPriority(profile.useCase, clientProfile.isForeground()));
+                getClientPriority(profile.useCase, checkIsForeground(pid)));
 
         addClientProfile(clientId[0], clientProfile, listener);
     }
@@ -547,8 +546,7 @@
             return false;
         }
 
-        profile.setForeground(checkIsForeground(profile.getProcessId()));
-        profile.setPriority(priority);
+        profile.overwritePriority(priority);
         profile.setNiceValue(niceValue);
 
         return true;
@@ -694,7 +692,7 @@
                 } else if (grantingFrontendHandle == TunerResourceManager.INVALID_RESOURCE_HANDLE) {
                     // Record the frontend id with the lowest client priority among all the
                     // in use frontends when no available frontend has been found.
-                    int priority = getOwnerClientPriority(fr.getOwnerClientId());
+                    int priority = updateAndGetOwnerClientPriority(fr.getOwnerClientId());
                     if (currentLowestPriority > priority) {
                         inUseLowestPriorityFrHandle = fr.getHandle();
                         currentLowestPriority = priority;
@@ -760,7 +758,7 @@
             } else {
                 // Record the lnb id with the lowest client priority among all the
                 // in use lnb when no available lnb has been found.
-                int priority = getOwnerClientPriority(lnb.getOwnerClientId());
+                int priority = updateAndGetOwnerClientPriority(lnb.getOwnerClientId());
                 if (currentLowestPriority > priority) {
                     inUseLowestPriorityLnbHandle = lnb.getHandle();
                     currentLowestPriority = priority;
@@ -818,7 +816,7 @@
         }
         for (int ownerId : cas.getOwnerClientIds()) {
             // Record the client id with lowest priority that is using the current Cas system.
-            int priority = getOwnerClientPriority(ownerId);
+            int priority = updateAndGetOwnerClientPriority(ownerId);
             if (currentLowestPriority > priority) {
                 lowestPriorityOwnerId = ownerId;
                 currentLowestPriority = priority;
@@ -867,7 +865,7 @@
         }
         for (int ownerId : ciCam.getOwnerClientIds()) {
             // Record the client id with lowest priority that is using the current Cas system.
-            int priority = getOwnerClientPriority(ownerId);
+            int priority = updateAndGetOwnerClientPriority(ownerId);
             if (currentLowestPriority > priority) {
                 lowestPriorityOwnerId = ownerId;
                 currentLowestPriority = priority;
@@ -966,18 +964,17 @@
     }
 
     @VisibleForTesting
-    // This mothod is to sync up the request client's foreground/background status and update
-    // the client priority accordingly whenever new resource request comes in.
-    protected void clientPriorityUpdateOnRequest(ClientProfile requestProfile) {
-        int pid = requestProfile.getProcessId();
-        boolean currentIsForeground = checkIsForeground(pid);
-        if (requestProfile.isForeground() == currentIsForeground) {
+    // This mothod is to sync up the request/holder client's foreground/background status and update
+    // the client priority accordingly whenever a new resource request comes in.
+    protected void clientPriorityUpdateOnRequest(ClientProfile profile) {
+        if (profile.isPriorityOverwritten()) {
             // To avoid overriding the priority set through updateClientPriority API.
             return;
         }
-        requestProfile.setForeground(currentIsForeground);
-        requestProfile.setPriority(
-                getClientPriority(requestProfile.getUseCase(), currentIsForeground));
+        int pid = profile.getProcessId();
+        boolean currentIsForeground = checkIsForeground(pid);
+        profile.setPriority(
+                getClientPriority(profile.getUseCase(), currentIsForeground));
     }
 
     @VisibleForTesting
@@ -1154,13 +1151,15 @@
     }
 
     /**
-     * Get the owner client's priority.
+     * Update and get the owner client's priority.
      *
      * @param clientId the owner client id.
      * @return the priority of the owner client.
      */
-    private int getOwnerClientPriority(int clientId) {
-        return getClientProfile(clientId).getPriority();
+    private int updateAndGetOwnerClientPriority(int clientId) {
+        ClientProfile profile = getClientProfile(clientId);
+        clientPriorityUpdateOnRequest(profile);
+        return profile.getPriority();
     }
 
     @VisibleForTesting
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 1426579..b31e42a 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -551,6 +551,8 @@
 
         new Thread(() -> {
             boolean enabled = false;
+            long nextWrite = 0;
+
             while (true) {
                 int maxFd = getMaxFd();
                 if (maxFd > enableThreshold) {
@@ -561,12 +563,30 @@
 
                 if (maxFd > enableThreshold && !enabled) {
                     Slog.i("System", "fdtrack enable threshold reached, enabling");
+                    FrameworkStatsLog.write(FrameworkStatsLog.FDTRACK_EVENT_OCCURRED,
+                            FrameworkStatsLog.FDTRACK_EVENT_OCCURRED__EVENT__ENABLED,
+                            maxFd);
+
                     System.loadLibrary("fdtrack");
                     enabled = true;
                 } else if (maxFd > abortThreshold) {
                     Slog.i("System", "fdtrack abort threshold reached, dumping and aborting");
+                    FrameworkStatsLog.write(FrameworkStatsLog.FDTRACK_EVENT_OCCURRED,
+                            FrameworkStatsLog.FDTRACK_EVENT_OCCURRED__EVENT__ABORTING,
+                            maxFd);
+
                     dumpHprof();
                     fdtrackAbort();
+                } else {
+                    // Limit this to once per hour.
+                    long now = SystemClock.elapsedRealtime();
+                    if (now > nextWrite) {
+                        nextWrite = now + 60 * 60 * 1000;
+                        FrameworkStatsLog.write(FrameworkStatsLog.FDTRACK_EVENT_OCCURRED,
+                                enabled ? FrameworkStatsLog.FDTRACK_EVENT_OCCURRED__EVENT__ENABLED
+                                        : FrameworkStatsLog.FDTRACK_EVENT_OCCURRED__EVENT__DISABLED,
+                                maxFd);
+                    }
                 }
 
                 try {
diff --git a/services/robotests/backup/src/com/android/server/backup/fullbackup/AppMetadataBackupWriterTest.java b/services/robotests/backup/src/com/android/server/backup/fullbackup/AppMetadataBackupWriterTest.java
index aca48b6..ee5a534 100644
--- a/services/robotests/backup/src/com/android/server/backup/fullbackup/AppMetadataBackupWriterTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/fullbackup/AppMetadataBackupWriterTest.java
@@ -19,8 +19,8 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageParser.SigningDetails;
 import android.content.pm.Signature;
+import android.content.pm.SigningDetails;
 import android.content.pm.SigningInfo;
 import android.os.Build;
 import android.os.Environment;
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
index db0c3ae..d4f17f3 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
@@ -21,10 +21,10 @@
 import android.content.pm.ApplicationInfo
 import android.content.pm.FallbackCategoryProvider
 import android.content.pm.FeatureInfo
-import android.content.pm.PackageParser.SigningDetails
 import android.content.pm.ResolveInfo
 import android.content.pm.ServiceInfo
 import android.content.pm.Signature
+import android.content.pm.SigningDetails
 import android.content.pm.UserInfo
 import android.content.pm.parsing.ParsingPackage
 import android.content.pm.parsing.ParsingPackageUtils
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
index 195cc01..65632df 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
@@ -227,8 +227,8 @@
         assertThat(destroyedNonReadySession.isDestroyed()).isTrue();
 
         mStagingManager.onBootCompletedBroadcastReceived();
-        assertThat(nonReadyApkSession.hasPreRebootVerificationStarted()).isTrue();
-        assertThat(nonReadyApexSession.hasPreRebootVerificationStarted()).isTrue();
+        assertThat(nonReadyApkSession.hasVerificationStarted()).isTrue();
+        assertThat(nonReadyApexSession.hasVerificationStarted()).isTrue();
     }
 
     @Test
@@ -519,7 +519,7 @@
         private int mParentSessionId = -1;
         private String mPackageName;
         private boolean mIsAbandonded = false;
-        private boolean mPreRebootVerificationStarted = false;
+        private boolean mVerificationStarted = false;
         private final List<StagingManager.StagedSession> mChildSessions = new ArrayList<>();
 
         private FakeStagedSession(int sessionId) {
@@ -550,8 +550,8 @@
             return mIsAbandonded;
         }
 
-        private boolean hasPreRebootVerificationStarted() {
-            return mPreRebootVerificationStarted;
+        private boolean hasVerificationStarted() {
+            return mVerificationStarted;
         }
 
         private FakeStagedSession addChildSession(FakeStagedSession session) {
@@ -706,20 +706,13 @@
         }
 
         @Override
-        public boolean notifyStartPreRebootVerification() {
-            mPreRebootVerificationStarted = true;
-            // TODO(ioffe): change to true when tests for pre-reboot verification are added.
-            return false;
-        }
-
-        @Override
         public void notifyEndPreRebootVerification() {
             throw new UnsupportedOperationException();
         }
 
         @Override
         public void verifySession() {
-            throw new UnsupportedOperationException();
+            mVerificationStarted = true;
         }
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/backup/utils/BackupEligibilityRulesTest.java b/services/tests/servicestests/src/com/android/server/backup/utils/BackupEligibilityRulesTest.java
index 7323096..7aea65e 100644
--- a/services/tests/servicestests/src/com/android/server/backup/utils/BackupEligibilityRulesTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/utils/BackupEligibilityRulesTest.java
@@ -20,7 +20,6 @@
 
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
 import android.app.backup.BackupManager.OperationType;
@@ -30,8 +29,8 @@
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.Property;
 import android.content.pm.PackageManagerInternal;
-import android.content.pm.PackageParser;
 import android.content.pm.Signature;
+import android.content.pm.SigningDetails;
 import android.content.pm.SigningInfo;
 import android.os.Process;
 import android.os.UserHandle;
@@ -41,7 +40,6 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.backup.UserBackupManagerService;
-import com.android.server.pm.parsing.pkg.AndroidPackage;
 
 import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
 import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
@@ -540,9 +538,9 @@
         PackageInfo packageInfo = new PackageInfo();
         packageInfo.packageName = "test";
         packageInfo.signingInfo = new SigningInfo(
-                new PackageParser.SigningDetails(
+                new SigningDetails(
                         new Signature[] {SIGNATURE_1},
-                        PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
+                        SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
                         null,
                         null));
         packageInfo.applicationInfo = new ApplicationInfo();
@@ -558,9 +556,9 @@
         PackageInfo packageInfo = new PackageInfo();
         packageInfo.packageName = "test";
         packageInfo.signingInfo = new SigningInfo(
-                new PackageParser.SigningDetails(
+                new SigningDetails(
                         new Signature[] {SIGNATURE_1},
-                        PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
+                        SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
                         null,
                         null));
         packageInfo.applicationInfo = new ApplicationInfo();
@@ -635,9 +633,9 @@
         PackageInfo packageInfo = new PackageInfo();
         packageInfo.packageName = "test";
         packageInfo.signingInfo = new SigningInfo(
-                new PackageParser.SigningDetails(
+                new SigningDetails(
                         new Signature[] {SIGNATURE_1, SIGNATURE_2, SIGNATURE_3},
-                        PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
+                        SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
                         null,
                         null));
         packageInfo.applicationInfo = new ApplicationInfo();
@@ -656,9 +654,9 @@
         PackageInfo packageInfo = new PackageInfo();
         packageInfo.packageName = "test";
         packageInfo.signingInfo = new SigningInfo(
-                new PackageParser.SigningDetails(
+                new SigningDetails(
                         new Signature[] {SIGNATURE_1, SIGNATURE_2, SIGNATURE_3},
-                        PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
+                        SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
                         null,
                         null));
         packageInfo.applicationInfo = new ApplicationInfo();
@@ -677,9 +675,9 @@
         PackageInfo packageInfo = new PackageInfo();
         packageInfo.packageName = "test";
         packageInfo.signingInfo = new SigningInfo(
-                new PackageParser.SigningDetails(
+                new SigningDetails(
                         new Signature[] {signature1Copy, signature2Copy},
-                        PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
+                        SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
                         null,
                         null));
         packageInfo.applicationInfo = new ApplicationInfo();
@@ -698,9 +696,9 @@
         PackageInfo packageInfo = new PackageInfo();
         packageInfo.packageName = "test";
         packageInfo.signingInfo = new SigningInfo(
-                new PackageParser.SigningDetails(
+                new SigningDetails(
                         new Signature[] {SIGNATURE_1, SIGNATURE_2, SIGNATURE_3},
-                        PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
+                        SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
                         null,
                         null));
         packageInfo.applicationInfo = new ApplicationInfo();
@@ -719,9 +717,9 @@
         PackageInfo packageInfo = new PackageInfo();
         packageInfo.packageName = "test";
         packageInfo.signingInfo = new SigningInfo(
-                new PackageParser.SigningDetails(
+                new SigningDetails(
                         new Signature[] {SIGNATURE_1},
-                        PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
+                        SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
                         null,
                         null));
         packageInfo.applicationInfo = new ApplicationInfo();
@@ -743,9 +741,9 @@
         PackageInfo packageInfo = new PackageInfo();
         packageInfo.packageName = "test";
         packageInfo.signingInfo = new SigningInfo(
-                new PackageParser.SigningDetails(
+                new SigningDetails(
                         new Signature[] {SIGNATURE_2},
-                        PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
+                        SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
                         null,
                         new Signature[] {SIGNATURE_1, SIGNATURE_2}));
         packageInfo.applicationInfo = new ApplicationInfo();
@@ -770,9 +768,9 @@
         PackageInfo packageInfo = new PackageInfo();
         packageInfo.packageName = "test";
         packageInfo.signingInfo = new SigningInfo(
-                new PackageParser.SigningDetails(
+                new SigningDetails(
                         new Signature[] {SIGNATURE_2},
-                        PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
+                        SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
                         null,
                         new Signature[] {SIGNATURE_1, SIGNATURE_2}));
         packageInfo.applicationInfo = new ApplicationInfo();
diff --git a/services/tests/servicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java b/services/tests/servicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java
index 5fcce67..e2536f8 100644
--- a/services/tests/servicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java
@@ -39,8 +39,8 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManagerInternal;
-import android.content.pm.PackageParser;
 import android.content.pm.Signature;
+import android.content.pm.SigningDetails;
 import android.content.pm.SigningInfo;
 import android.os.Bundle;
 import android.os.Process;
@@ -376,9 +376,9 @@
         packageInfo.applicationInfo.uid = Process.FIRST_APPLICATION_UID;
         packageInfo.applicationInfo.backupAgentName = null;
         packageInfo.signingInfo = new SigningInfo(
-                new PackageParser.SigningDetails(
+                new SigningDetails(
                         new Signature[] {FAKE_SIGNATURE_2},
-                        PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
+                        SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
                         null,
                         null));
         PackageManagerStub.sPackageInfo = packageInfo;
@@ -413,9 +413,9 @@
         packageInfo.applicationInfo.uid = Process.SYSTEM_UID;
         packageInfo.applicationInfo.backupAgentName = "backup.agent";
         packageInfo.signingInfo = new SigningInfo(
-                new PackageParser.SigningDetails(
+                new SigningDetails(
                         new Signature[] {FAKE_SIGNATURE_1},
-                        PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
+                        SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
                         null,
                         null));
         PackageManagerStub.sPackageInfo = packageInfo;
@@ -451,9 +451,9 @@
         packageInfo.applicationInfo.uid = Process.FIRST_APPLICATION_UID;
         packageInfo.applicationInfo.backupAgentName = null;
         packageInfo.signingInfo = new SigningInfo(
-                new PackageParser.SigningDetails(
+                new SigningDetails(
                         new Signature[] {FAKE_SIGNATURE_1},
-                        PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
+                        SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
                         null,
                         null));
         PackageManagerStub.sPackageInfo = packageInfo;
@@ -492,9 +492,9 @@
         packageInfo.applicationInfo.uid = Process.FIRST_APPLICATION_UID;
         packageInfo.applicationInfo.backupAgentName = null;
         packageInfo.signingInfo = new SigningInfo(
-                new PackageParser.SigningDetails(
+                new SigningDetails(
                         new Signature[] {FAKE_SIGNATURE_1},
-                        PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
+                        SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
                         null,
                         null));
         packageInfo.versionCode = 2;
@@ -536,9 +536,9 @@
         packageInfo.applicationInfo.uid = Process.FIRST_APPLICATION_UID;
         packageInfo.applicationInfo.backupAgentName = null;
         packageInfo.signingInfo = new SigningInfo(
-                new PackageParser.SigningDetails(
+                new SigningDetails(
                         new Signature[] {FAKE_SIGNATURE_1},
-                        PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
+                        SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
                         null,
                         null));
         packageInfo.versionCode = 1;
@@ -576,9 +576,9 @@
         packageInfo.applicationInfo.uid = Process.FIRST_APPLICATION_UID;
         packageInfo.applicationInfo.backupAgentName = null;
         packageInfo.signingInfo = new SigningInfo(
-                new PackageParser.SigningDetails(
+                new SigningDetails(
                         new Signature[] {FAKE_SIGNATURE_1},
-                        PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
+                        SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
                         null,
                         null));
         packageInfo.versionCode = 1;
diff --git a/services/tests/servicestests/src/com/android/server/display/color/ColorDisplayServiceTest.java b/services/tests/servicestests/src/com/android/server/display/color/ColorDisplayServiceTest.java
index a19b387..363c26b 100644
--- a/services/tests/servicestests/src/com/android/server/display/color/ColorDisplayServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/color/ColorDisplayServiceTest.java
@@ -21,7 +21,6 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -53,9 +52,7 @@
 import com.android.server.twilight.TwilightState;
 
 import org.junit.After;
-import org.junit.AfterClass;
 import org.junit.Before;
-import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mockito;
@@ -76,25 +73,29 @@
     private int mUserId;
 
     private MockTwilightManager mTwilightManager;
+    private DisplayTransformManager mDisplayTransformManager;
 
     private ColorDisplayService mCds;
     private ColorDisplayService.BinderService mBinderService;
 
     private Resources mResourcesSpy;
 
-    @BeforeClass
-    public static void setDtm() {
-        final DisplayTransformManager dtm = Mockito.mock(DisplayTransformManager.class);
-        LocalServices.addService(DisplayTransformManager.class, dtm);
-    }
+    private static final int[] MINIMAL_COLOR_MODES = new int[] {
+        ColorDisplayManager.COLOR_MODE_NATURAL,
+        ColorDisplayManager.COLOR_MODE_BOOSTED,
+    };
 
     @Before
     public void setUp() {
         mContext = Mockito.spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
         doReturn(mContext).when(mContext).getApplicationContext();
 
-        mResourcesSpy = Mockito.spy(mContext.getResources());
-        when(mContext.getResources()).thenReturn(mResourcesSpy);
+        final Resources res = Mockito.spy(mContext.getResources());
+        doReturn(MINIMAL_COLOR_MODES).when(res).getIntArray(R.array.config_availableColorModes);
+        doReturn(true).when(res).getBoolean(R.bool.config_nightDisplayAvailable);
+        doReturn(true).when(res).getBoolean(R.bool.config_displayWhiteBalanceAvailable);
+        when(mContext.getResources()).thenReturn(res);
+        mResourcesSpy = res;
 
         mUserId = ActivityManager.getCurrentUser();
 
@@ -108,6 +109,10 @@
         mTwilightManager = new MockTwilightManager();
         LocalServices.addService(TwilightManager.class, mTwilightManager);
 
+        mDisplayTransformManager = Mockito.mock(DisplayTransformManager.class);
+        doReturn(true).when(mDisplayTransformManager).needsLinearColorMatrix();
+        LocalServices.addService(DisplayTransformManager.class, mDisplayTransformManager);
+
         mCds = new ColorDisplayService(mContext);
         mBinderService = mCds.new BinderService();
         LocalServices.addService(ColorDisplayService.ColorDisplayServiceInternal.class,
@@ -116,12 +121,18 @@
 
     @After
     public void tearDown() {
-        LocalServices.removeServiceForTest(TwilightManager.class);
-
+        /*
+         * Wait for internal {@link Handler} to finish processing pending messages, so that test
+         * code can safelyremove {@link DisplayTransformManager} mock from {@link LocalServices}.
+         */
+        mCds.mHandler.runWithScissors(() -> { /* nop */ }, /* timeout */ 1000);
         mCds = null;
 
+        LocalServices.removeServiceForTest(TwilightManager.class);
         mTwilightManager = null;
 
+        LocalServices.removeServiceForTest(DisplayTransformManager.class);
+
         mUserId = UserHandle.USER_NULL;
         mContext = null;
 
@@ -130,11 +141,6 @@
         LocalServices.removeServiceForTest(ColorDisplayService.ColorDisplayServiceInternal.class);
     }
 
-    @AfterClass
-    public static void removeDtm() {
-        LocalServices.removeServiceForTest(DisplayTransformManager.class);
-    }
-
     @Test
     public void customSchedule_whenStartedAfterNight_ifOffAfterNight_turnsOff() {
         setAutoModeCustom(-120 /* startTimeOffset */, -60 /* endTimeOffset */);
@@ -1064,24 +1070,18 @@
 
     @Test
     public void compositionColorSpaces_noResources() {
-        final DisplayTransformManager dtm = LocalServices.getService(DisplayTransformManager.class);
-        reset(dtm);
-
         when(mResourcesSpy.getIntArray(R.array.config_displayCompositionColorModes))
             .thenReturn(new int[] {});
         when(mResourcesSpy.getIntArray(R.array.config_displayCompositionColorSpaces))
             .thenReturn(new int[] {});
         setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
         startService();
-        verify(dtm).setColorMode(eq(ColorDisplayManager.COLOR_MODE_NATURAL), any(),
-                eq(Display.COLOR_MODE_INVALID));
+        verify(mDisplayTransformManager).setColorMode(
+                eq(ColorDisplayManager.COLOR_MODE_NATURAL), any(), eq(Display.COLOR_MODE_INVALID));
     }
 
     @Test
     public void compositionColorSpaces_invalidResources() {
-        final DisplayTransformManager dtm = LocalServices.getService(DisplayTransformManager.class);
-        reset(dtm);
-
         when(mResourcesSpy.getIntArray(R.array.config_displayCompositionColorModes))
             .thenReturn(new int[] {
                ColorDisplayManager.COLOR_MODE_NATURAL,
@@ -1094,15 +1094,12 @@
             });
         setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
         startService();
-        verify(dtm).setColorMode(eq(ColorDisplayManager.COLOR_MODE_NATURAL), any(),
-                eq(Display.COLOR_MODE_INVALID));
+        verify(mDisplayTransformManager).setColorMode(
+                eq(ColorDisplayManager.COLOR_MODE_NATURAL), any(), eq(Display.COLOR_MODE_INVALID));
     }
 
     @Test
     public void compositionColorSpaces_validResources_validColorMode() {
-        final DisplayTransformManager dtm = LocalServices.getService(DisplayTransformManager.class);
-        reset(dtm);
-
         when(mResourcesSpy.getIntArray(R.array.config_displayCompositionColorModes))
             .thenReturn(new int[] {
                ColorDisplayManager.COLOR_MODE_NATURAL
@@ -1113,15 +1110,12 @@
             });
         setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
         startService();
-        verify(dtm).setColorMode(eq(ColorDisplayManager.COLOR_MODE_NATURAL), any(),
-                eq(Display.COLOR_MODE_SRGB));
+        verify(mDisplayTransformManager).setColorMode(
+                eq(ColorDisplayManager.COLOR_MODE_NATURAL), any(), eq(Display.COLOR_MODE_SRGB));
     }
 
     @Test
     public void compositionColorSpaces_validResources_invalidColorMode() {
-        final DisplayTransformManager dtm = LocalServices.getService(DisplayTransformManager.class);
-        reset(dtm);
-
         when(mResourcesSpy.getIntArray(R.array.config_displayCompositionColorModes))
             .thenReturn(new int[] {
                ColorDisplayManager.COLOR_MODE_NATURAL
@@ -1132,8 +1126,8 @@
             });
         setColorMode(ColorDisplayManager.COLOR_MODE_BOOSTED);
         startService();
-        verify(dtm).setColorMode(eq(ColorDisplayManager.COLOR_MODE_BOOSTED), any(),
-                eq(Display.COLOR_MODE_INVALID));
+        verify(mDisplayTransformManager).setColorMode(
+                eq(ColorDisplayManager.COLOR_MODE_BOOSTED), any(), eq(Display.COLOR_MODE_INVALID));
     }
 
     /**
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
index 80da696..5d0b00b 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -697,9 +697,12 @@
                 mHdmiCecLocalDevicePlayback.mAddress, ADDR_TV);
         HdmiCecMessage standbyMessageBroadcast = HdmiCecMessageBuilder.buildStandby(
                 mHdmiCecLocalDevicePlayback.mAddress, ADDR_BROADCAST);
+        HdmiCecMessage inactiveSource = HdmiCecMessageBuilder.buildInactiveSource(
+                mPlaybackLogicalAddress, mPlaybackPhysicalAddress);
 
         assertThat(mNativeWrapper.getResultMessages()).doesNotContain(standbyMessageToTv);
         assertThat(mNativeWrapper.getResultMessages()).doesNotContain(standbyMessageBroadcast);
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(inactiveSource);
     }
 
     @Test
@@ -719,9 +722,12 @@
                 mHdmiCecLocalDevicePlayback.mAddress, ADDR_TV);
         HdmiCecMessage standbyMessageBroadcast = HdmiCecMessageBuilder.buildStandby(
                 mHdmiCecLocalDevicePlayback.mAddress, ADDR_BROADCAST);
+        HdmiCecMessage inactiveSource = HdmiCecMessageBuilder.buildInactiveSource(
+                mPlaybackLogicalAddress, mPlaybackPhysicalAddress);
 
         assertThat(mNativeWrapper.getResultMessages()).doesNotContain(standbyMessageToTv);
         assertThat(mNativeWrapper.getResultMessages()).doesNotContain(standbyMessageBroadcast);
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(inactiveSource);
     }
 
     @Test
@@ -741,9 +747,12 @@
                 mHdmiCecLocalDevicePlayback.mAddress, ADDR_TV);
         HdmiCecMessage standbyMessageBroadcast = HdmiCecMessageBuilder.buildStandby(
                 mHdmiCecLocalDevicePlayback.mAddress, ADDR_BROADCAST);
+        HdmiCecMessage inactiveSource = HdmiCecMessageBuilder.buildInactiveSource(
+                mPlaybackLogicalAddress, mPlaybackPhysicalAddress);
 
         assertThat(mNativeWrapper.getResultMessages()).doesNotContain(standbyMessageToTv);
         assertThat(mNativeWrapper.getResultMessages()).doesNotContain(standbyMessageBroadcast);
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(inactiveSource);
     }
 
     @Test
@@ -763,9 +772,12 @@
                 mHdmiCecLocalDevicePlayback.mAddress, ADDR_TV);
         HdmiCecMessage standbyMessageBroadcast = HdmiCecMessageBuilder.buildStandby(
                 mHdmiCecLocalDevicePlayback.mAddress, ADDR_BROADCAST);
+        HdmiCecMessage inactiveSource = HdmiCecMessageBuilder.buildInactiveSource(
+                mPlaybackLogicalAddress, mPlaybackPhysicalAddress);
 
         assertThat(mNativeWrapper.getResultMessages()).contains(standbyMessageToTv);
         assertThat(mNativeWrapper.getResultMessages()).doesNotContain(standbyMessageBroadcast);
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(inactiveSource);
     }
 
     @Test
@@ -785,9 +797,12 @@
                 mHdmiCecLocalDevicePlayback.mAddress, ADDR_TV);
         HdmiCecMessage standbyMessageBroadcast = HdmiCecMessageBuilder.buildStandby(
                 mHdmiCecLocalDevicePlayback.mAddress, ADDR_BROADCAST);
+        HdmiCecMessage inactiveSource = HdmiCecMessageBuilder.buildInactiveSource(
+                mPlaybackLogicalAddress, mPlaybackPhysicalAddress);
 
         assertThat(mNativeWrapper.getResultMessages()).doesNotContain(standbyMessageToTv);
         assertThat(mNativeWrapper.getResultMessages()).contains(standbyMessageBroadcast);
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(inactiveSource);
     }
 
     @Test
@@ -807,9 +822,12 @@
                 mHdmiCecLocalDevicePlayback.mAddress, ADDR_TV);
         HdmiCecMessage standbyMessageBroadcast = HdmiCecMessageBuilder.buildStandby(
                 mHdmiCecLocalDevicePlayback.mAddress, ADDR_BROADCAST);
+        HdmiCecMessage inactiveSource = HdmiCecMessageBuilder.buildInactiveSource(
+                mPlaybackLogicalAddress, mPlaybackPhysicalAddress);
 
         assertThat(mNativeWrapper.getResultMessages()).doesNotContain(standbyMessageToTv);
         assertThat(mNativeWrapper.getResultMessages()).doesNotContain(standbyMessageBroadcast);
+        assertThat(mNativeWrapper.getResultMessages()).contains(inactiveSource);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java
new file mode 100644
index 0000000..0ed6d7be
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java
@@ -0,0 +1,280 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.hdmi;
+
+import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
+import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
+import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
+import static com.android.server.hdmi.Constants.ADDR_TUNER_1;
+import static com.android.server.hdmi.Constants.ADDR_TV;
+import static com.android.server.hdmi.Constants.ADDR_UNREGISTERED;
+import static com.android.server.hdmi.Constants.MESSAGE_ACTIVE_SOURCE;
+import static com.android.server.hdmi.Constants.MESSAGE_ROUTING_INFORMATION;
+import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
+import static com.android.server.hdmi.RoutingControlAction.STATE_WAIT_FOR_ROUTING_INFORMATION;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.hardware.hdmi.HdmiDeviceInfo;
+import android.hardware.hdmi.HdmiPortInfo;
+import android.hardware.hdmi.IHdmiControlCallback;
+import android.os.Handler;
+import android.os.IPowerManager;
+import android.os.IThermalService;
+import android.os.Looper;
+import android.os.PowerManager;
+import android.os.test.TestLooper;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+
+import com.android.server.hdmi.HdmiCecFeatureAction.ActionTimer;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@SmallTest
+@RunWith(JUnit4.class)
+public class RoutingControlActionTest {
+    /*
+     * Example connection diagram used in tests. Double-lined paths indicate the currently active
+     * routes.
+     *
+     *
+     *                              +-----------+
+     *                              |    TV     |
+     *                              |  0.0.0.0  |
+     *                              +---+-----+-+
+     *                                  |     |
+     *                               <----------+ 1) AVR -> Switch
+     *             +----------+         |     |  +-----------+
+     *             | AVR      +---------+     +--+ Switch    |
+     *             | 1.0.0.0  |                  | 2.0.0.0   |
+     *             +--+---++--+                  +--++-----+-+  <-------+ 2) Recorder -> Blu-ray
+     *                |   ||                        ||     |
+     *                |   ||                        ||     +--------+
+     * +-----------+  |   ||  +----------+     +----++----+         |
+     * | XBox      +--+   ++--+ Tuner    |     | Blueray  |   +-----+----+
+     * | 1.1.0.0   |          | 1.2.0.0  |     | 2.1.0.0  |   | Recorder |
+     * +-----------+          +----++----+     +----------+   | 2.2.0.0  |
+     *                             ||                         +----------+
+     *                             ||
+     *                        +----++----+
+     *                        | Player   |
+     *                        | 1.2.1.0  |
+     *                        +----------+
+     *
+     */
+
+    private static final int PHYSICAL_ADDRESS_TV = 0x0000;
+    private static final int PHYSICAL_ADDRESS_AVR = 0x1000;
+    private static final int PHYSICAL_ADDRESS_SWITCH = 0x2000;
+    private static final int PHYSICAL_ADDRESS_TUNER = 0x1200;
+    private static final int PHYSICAL_ADDRESS_PLAYER = 0x1210;
+    private static final int PHYSICAL_ADDRESS_BLUERAY = 0x2100;
+    private static final int PHYSICAL_ADDRESS_RECORDER = 0x2200;
+    private static final int PORT_1 = 1;
+    private static final int PORT_2 = 2;
+    private static final int VENDOR_ID_AVR = 0x11233;
+
+    private static final byte[] TUNER_PARAM =
+            new byte[] {(PHYSICAL_ADDRESS_TUNER >> 8) & 0xFF, PHYSICAL_ADDRESS_TUNER & 0xFF};
+    private static final byte[] PLAYER_PARAM =
+            new byte[] {(PHYSICAL_ADDRESS_PLAYER >> 8) & 0xFF, PHYSICAL_ADDRESS_PLAYER & 0xFF};
+
+    private static final HdmiDeviceInfo DEVICE_INFO_AVR =
+            new HdmiDeviceInfo(ADDR_AUDIO_SYSTEM, PHYSICAL_ADDRESS_AVR, PORT_1,
+                    HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM, VENDOR_ID_AVR, "Audio");
+    private static final HdmiDeviceInfo DEVICE_INFO_PLAYER =
+            new HdmiDeviceInfo(ADDR_PLAYBACK_1, PHYSICAL_ADDRESS_PLAYER, PORT_1,
+                    HdmiDeviceInfo.DEVICE_PLAYBACK, VENDOR_ID_AVR, "Player");
+    private static final HdmiCecMessage ROUTING_INFORMATION_TUNER = new HdmiCecMessage(
+            ADDR_UNREGISTERED, ADDR_BROADCAST, MESSAGE_ROUTING_INFORMATION, TUNER_PARAM);
+    private static final HdmiCecMessage ROUTING_INFORMATION_PLAYER = new HdmiCecMessage(
+            ADDR_UNREGISTERED, ADDR_BROADCAST, MESSAGE_ROUTING_INFORMATION, PLAYER_PARAM);
+    private static final HdmiCecMessage ACTIVE_SOURCE_TUNER = new HdmiCecMessage(
+            ADDR_TUNER_1, ADDR_BROADCAST, MESSAGE_ACTIVE_SOURCE, TUNER_PARAM);
+    private static final HdmiCecMessage ACTIVE_SOURCE_PLAYER = new HdmiCecMessage(
+            ADDR_PLAYBACK_1, ADDR_BROADCAST, MESSAGE_ACTIVE_SOURCE, PLAYER_PARAM);
+
+    private HdmiControlService mHdmiControlService;
+    private HdmiCecController mHdmiCecController;
+    private HdmiCecLocalDeviceTv mHdmiCecLocalDeviceTv;
+    private FakeNativeWrapper mNativeWrapper;
+    private Looper mMyLooper;
+    private TestLooper mTestLooper = new TestLooper();
+    private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>();
+
+    @Mock
+    private IPowerManager mIPowerManagerMock;
+    @Mock
+    private IThermalService mIThermalServiceMock;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        Context context = InstrumentationRegistry.getTargetContext();
+        mMyLooper = mTestLooper.getLooper();
+        PowerManager powerManager = new PowerManager(context, mIPowerManagerMock,
+                mIThermalServiceMock, new Handler(mMyLooper));
+
+        HdmiCecConfig hdmiCecConfig = new FakeHdmiCecConfig(context);
+
+        mHdmiControlService =
+                new HdmiControlService(InstrumentationRegistry.getTargetContext()) {
+                    @Override
+                    boolean isControlEnabled() {
+                        return true;
+                    }
+
+                    @Override
+                    void wakeUp() {
+                    }
+
+                    @Override
+                    protected void writeStringSystemProperty(String key, String value) {
+                        // do nothing
+                    }
+
+                    @Override
+                    boolean isPowerStandbyOrTransient() {
+                        return false;
+                    }
+
+                    @Override
+                    protected PowerManager getPowerManager() {
+                        return powerManager;
+                    }
+
+                    @Override
+                    protected HdmiCecConfig getHdmiCecConfig() {
+                        return hdmiCecConfig;
+                    }
+                };
+
+        mHdmiCecLocalDeviceTv = new HdmiCecLocalDeviceTv(mHdmiControlService);
+        mHdmiCecLocalDeviceTv.init();
+        mHdmiControlService.setIoLooper(mMyLooper);
+        mNativeWrapper = new FakeNativeWrapper();
+        mHdmiCecController = HdmiCecController.createWithNativeWrapper(
+                mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
+        mHdmiControlService.setCecController(mHdmiCecController);
+        mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
+        mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));
+        mLocalDevices.add(mHdmiCecLocalDeviceTv);
+        HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[1];
+        hdmiPortInfos[0] =
+                new HdmiPortInfo(1, HdmiPortInfo.PORT_INPUT, PHYSICAL_ADDRESS_AVR,
+                                 true, false, false);
+        mNativeWrapper.setPortInfo(hdmiPortInfos);
+        mHdmiControlService.initService();
+        mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+        mNativeWrapper.setPhysicalAddress(0x0000);
+        mTestLooper.dispatchAll();
+        mNativeWrapper.clearResultMessages();
+        mHdmiControlService.getHdmiCecNetwork().addCecDevice(DEVICE_INFO_AVR);
+    }
+
+    private static class TestActionTimer implements ActionTimer {
+        private int mState;
+
+        @Override
+        public void sendTimerMessage(int state, long delayMillis) {
+            mState = state;
+        }
+
+        @Override
+        public void clearTimerMessage() {
+        }
+
+        private int getState() {
+            return mState;
+        }
+    }
+
+    private static class TestInputSelectCallback extends IHdmiControlCallback.Stub {
+        private final List<Integer> mCallbackResult = new ArrayList<Integer>();
+
+        @Override
+        public void onComplete(int result) {
+            mCallbackResult.add(result);
+        }
+
+        private int getResult() {
+            assert (mCallbackResult.size() == 1);
+            return mCallbackResult.get(0);
+        }
+    }
+
+    private static RoutingControlAction createRoutingControlAction(HdmiCecLocalDeviceTv localDevice,
+            TestInputSelectCallback callback) {
+        return new RoutingControlAction(localDevice, PHYSICAL_ADDRESS_AVR, callback);
+    }
+
+    // Routing control succeeds against the device connected directly to the port. Action
+    // won't get any <Routing Information> in this case. It times out on <Routing Information>,
+    // regards the directly connected one as the new routing path to switch to.
+    @Test
+    public void testRoutingControl_succeedForDirectlyConnectedDevice() {
+        TestInputSelectCallback callback = new TestInputSelectCallback();
+        TestActionTimer actionTimer = new TestActionTimer();
+        mHdmiControlService.getHdmiCecNetwork().addCecDevice(DEVICE_INFO_AVR);
+
+        RoutingControlAction action = createRoutingControlAction(mHdmiCecLocalDeviceTv, callback);
+        action.setActionTimer(actionTimer);
+        action.start();
+        assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_ROUTING_INFORMATION);
+
+        action.handleTimerEvent(actionTimer.getState());
+        mTestLooper.dispatchAll();
+        HdmiCecMessage setStreamPath = HdmiCecMessageBuilder.buildSetStreamPath(
+                        ADDR_TV, PHYSICAL_ADDRESS_AVR);
+        assertThat(mNativeWrapper.getResultMessages()).contains(setStreamPath);
+    }
+
+    // Succeeds by receiving a couple of <Routing Information> commands, followed by
+    // <Set Stream Path> going out in the end.
+    @Test
+    public void testRoutingControl_succeedForDeviceBehindSwitch() {
+        TestInputSelectCallback callback = new TestInputSelectCallback();
+        TestActionTimer actionTimer = new TestActionTimer();
+        mHdmiControlService.getHdmiCecNetwork().addCecDevice(DEVICE_INFO_PLAYER);
+        RoutingControlAction action = createRoutingControlAction(mHdmiCecLocalDeviceTv, callback);
+        action.setActionTimer(actionTimer);
+        action.start();
+
+        assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_ROUTING_INFORMATION);
+
+        action.processCommand(ROUTING_INFORMATION_TUNER);
+        action.processCommand(ROUTING_INFORMATION_PLAYER);
+
+        action.handleTimerEvent(actionTimer.getState());
+        mTestLooper.dispatchAll();
+        HdmiCecMessage setStreamPath = HdmiCecMessageBuilder.buildSetStreamPath(
+                        ADDR_TV, PHYSICAL_ADDRESS_PLAYER);
+        assertThat(mNativeWrapper.getResultMessages()).contains(setStreamPath);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java b/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java
index 9f428c7..1088ed5 100644
--- a/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java
@@ -30,9 +30,8 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageParser;
-import android.content.pm.PackageParser.SigningDetails;
 import android.content.pm.Signature;
+import android.content.pm.SigningDetails;
 import android.content.pm.UserInfo;
 import android.content.pm.parsing.ParsingPackage;
 import android.content.pm.parsing.component.ParsedActivity;
@@ -252,8 +251,8 @@
         final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
         watcher.register();
         final Signature frameworkSignature = Mockito.mock(Signature.class);
-        final PackageParser.SigningDetails frameworkSigningDetails =
-                new PackageParser.SigningDetails(new Signature[]{frameworkSignature}, 1);
+        final SigningDetails frameworkSigningDetails =
+                new SigningDetails(new Signature[]{frameworkSignature}, 1);
         final ParsingPackage android = pkg("android");
         watcher.verifyNoChangeReported("prepare");
         android.addProtectedBroadcast("TEST_ACTION");
@@ -592,12 +591,12 @@
         appsFilter.onSystemReady();
 
         final Signature frameworkSignature = Mockito.mock(Signature.class);
-        final PackageParser.SigningDetails frameworkSigningDetails =
-                new PackageParser.SigningDetails(new Signature[]{frameworkSignature}, 1);
+        final SigningDetails frameworkSigningDetails =
+                new SigningDetails(new Signature[]{frameworkSignature}, 1);
 
         final Signature otherSignature = Mockito.mock(Signature.class);
-        final PackageParser.SigningDetails otherSigningDetails =
-                new PackageParser.SigningDetails(new Signature[]{otherSignature}, 1);
+        final SigningDetails otherSigningDetails =
+                new SigningDetails(new Signature[]{otherSignature}, 1);
 
         simulateAddPackage(appsFilter, pkg("android"), 1000,
                 b -> b.setSigningDetails(frameworkSigningDetails));
@@ -1185,8 +1184,8 @@
 
     private void simulateAddBasicAndroid(AppsFilter appsFilter) throws Exception {
         final Signature frameworkSignature = Mockito.mock(Signature.class);
-        final PackageParser.SigningDetails frameworkSigningDetails =
-                new PackageParser.SigningDetails(new Signature[]{frameworkSignature}, 1);
+        final SigningDetails frameworkSigningDetails =
+                new SigningDetails(new Signature[]{frameworkSignature}, 1);
         final ParsingPackage android = pkg("android");
         simulateAddPackage(appsFilter, android, 1000,
                 b -> b.setSigningDetails(frameworkSigningDetails));
diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
index 589b3b4..de178b0 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
@@ -75,12 +75,12 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
-import android.content.pm.PackageParser;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ShortcutInfo;
 import android.content.pm.ShortcutManager;
 import android.content.pm.ShortcutServiceInternal;
 import android.content.pm.Signature;
+import android.content.pm.SigningDetails;
 import android.content.pm.SigningInfo;
 import android.content.pm.UserInfo;
 import android.content.res.Resources;
@@ -1392,9 +1392,9 @@
         pi.applicationInfo.setVersionCode(version);
         pi.signatures = null;
         pi.signingInfo = new SigningInfo(
-                new PackageParser.SigningDetails(
+                new SigningDetails(
                         genSignatures(signatures),
-                        PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
+                        SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
                         null,
                         null));
         return pi;
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
index 128cbaa..7217877 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
@@ -35,10 +35,10 @@
 import android.content.pm.FeatureInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager.Property;
-import android.content.pm.PackageParser;
 import android.content.pm.PackageUserState;
 import android.content.pm.ServiceInfo;
 import android.content.pm.Signature;
+import android.content.pm.SigningDetails;
 import android.content.pm.parsing.ParsingPackage;
 import android.content.pm.parsing.component.ParsedActivity;
 import android.content.pm.parsing.component.ParsedComponent;
@@ -645,7 +645,8 @@
         assertBundleApproximateEquals(a.getMetaData(), b.getMetaData());
         assertEquals(a.getVersionName(), b.getVersionName());
         assertEquals(a.getSharedUserId(), b.getSharedUserId());
-        assertArrayEquals(a.getSigningDetails().signatures, b.getSigningDetails().signatures);
+        assertArrayEquals(a.getSigningDetails().getSignatures(),
+                b.getSigningDetails().getSignatures());
         assertEquals(a.getRestrictedAccountType(), b.getRestrictedAccountType());
         assertEquals(a.getRequiredAccountType(), b.getRequiredAccountType());
         assertEquals(a.getOverlayTarget(), b.getOverlayTarget());
@@ -653,7 +654,7 @@
         assertEquals(a.getOverlayCategory(), b.getOverlayCategory());
         assertEquals(a.getOverlayPriority(), b.getOverlayPriority());
         assertEquals(a.isOverlayIsStatic(), b.isOverlayIsStatic());
-        assertEquals(a.getSigningDetails().publicKeys, b.getSigningDetails().publicKeys);
+        assertEquals(a.getSigningDetails().getPublicKeys(), b.getSigningDetails().getPublicKeys());
         assertEquals(a.getUpgradeKeySets(), b.getUpgradeKeySets());
         assertEquals(a.getKeySetMapping(), b.getKeySetMapping());
         assertArrayEquals(a.getRestrictUpdateHash(), b.getRestrictUpdateHash());
@@ -873,9 +874,9 @@
                 .setVersionName("foo17")
                 .setSharedUserId("foo18")
                 .setSigningDetails(
-                        new PackageParser.SigningDetails(
+                        new SigningDetails(
                                 new Signature[]{new Signature(new byte[16])},
-                                2,
+                                SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V2,
                                 new ArraySet<>(),
                                 null)
                 )
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java b/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java
index f75751b..f551ad1 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java
@@ -16,8 +16,8 @@
 
 package com.android.server.pm;
 
-import android.content.pm.PackageParser;
 import android.content.pm.PackageUserState;
+import android.content.pm.SigningDetails;
 import android.util.ArraySet;
 import android.util.SparseArray;
 
@@ -47,7 +47,7 @@
     private String[] mUsesStaticLibraries;
     private long[] mUsesStaticLibrariesVersions;
     private Map<String, ArraySet<String>> mMimeGroups;
-    private PackageParser.SigningDetails mSigningDetails;
+    private SigningDetails mSigningDetails;
     private UUID mDomainSetId = UUID.randomUUID();
 
     public PackageSettingBuilder setPackage(AndroidPackage pkg) {
@@ -159,7 +159,7 @@
     }
 
     public PackageSettingBuilder setSigningDetails(
-            PackageParser.SigningDetails signingDetails) {
+            SigningDetails signingDetails) {
         mSigningDetails = signingDetails;
         return this;
     }
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageSignaturesTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageSignaturesTest.java
index 27f3eec..b9431bf 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageSignaturesTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageSignaturesTest.java
@@ -22,11 +22,9 @@
 import static org.junit.Assert.fail;
 
 import android.content.Context;
-import android.content.pm.PackageParser;
 import android.content.pm.Signature;
+import android.content.pm.SigningDetails;
 import android.util.TypedXmlPullParser;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
 import android.util.Xml;
 
 import androidx.test.InstrumentationRegistry;
@@ -39,7 +37,6 @@
 import org.junit.runner.RunWith;
 import org.xmlpull.v1.XmlPullParser;
 
-import java.io.File;
 import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -107,10 +104,10 @@
     }
 
     private static final int[] CAPABILITIES =
-            {PackageParser.SigningDetails.CertCapabilities.INSTALLED_DATA,
-                    PackageParser.SigningDetails.CertCapabilities.SHARED_USER_ID,
-                    PackageParser.SigningDetails.CertCapabilities.PERMISSION,
-                    PackageParser.SigningDetails.CertCapabilities.ROLLBACK};
+            {SigningDetails.CertCapabilities.INSTALLED_DATA,
+                    SigningDetails.CertCapabilities.SHARED_USER_ID,
+                    SigningDetails.CertCapabilities.PERMISSION,
+                    SigningDetails.CertCapabilities.ROLLBACK};
 
     @Before
     public void setUp() throws Exception {
@@ -173,7 +170,7 @@
         assertEquals(
                 "The signing details was not UNKNOWN after parsing an invalid public key cert key"
                         + " attribute",
-                PackageParser.SigningDetails.UNKNOWN, mPackageSetting.signatures.mSigningDetails);
+                SigningDetails.UNKNOWN, mPackageSetting.signatures.mSigningDetails);
     }
 
     @Test
@@ -181,14 +178,14 @@
         // Verifies if the sigs count attribute is missing then the signature cannot be read but the
         // method does not throw an exception.
         verifyReadXmlReturnsExpectedSignatures("xml/one-signer-missing-sigs-count.xml",
-                PackageParser.SigningDetails.SignatureSchemeVersion.UNKNOWN);
+                SigningDetails.SignatureSchemeVersion.UNKNOWN);
     }
 
     @Test
     public void testReadXmlWithMissingSchemeVersion() throws Exception {
         // Verifies if the schemeVersion is an invalid value the signature can still be obtained.
         verifyReadXmlReturnsExpectedSignatures("xml/one-signer-missing-scheme-version.xml",
-                PackageParser.SigningDetails.SignatureSchemeVersion.UNKNOWN,
+                SigningDetails.SignatureSchemeVersion.UNKNOWN,
                 FIRST_EXPECTED_SIGNATURE);
     }
 
@@ -198,7 +195,7 @@
         // obtained.
         verifyReadXmlReturnsExpectedSignaturesAndLineage(
                 "xml/three-signers-in-lineage-missing-scheme-version.xml",
-                PackageParser.SigningDetails.SignatureSchemeVersion.UNKNOWN,
+                SigningDetails.SignatureSchemeVersion.UNKNOWN,
                 FIRST_EXPECTED_SIGNATURE, SECOND_EXPECTED_SIGNATURE, THIRD_EXPECTED_SIGNATURE);
     }
 
@@ -386,7 +383,7 @@
         verifySignaturesContainExpectedValues(signatures, expectedSignatures);
         assertEquals("The returned signature scheme is not the expected value",
                 expectedSchemeVersion,
-                mPackageSetting.signatures.mSigningDetails.signatureSchemeVersion);
+                mPackageSetting.signatures.mSigningDetails.getSignatureSchemeVersion());
     }
 
     /**
@@ -402,7 +399,7 @@
         Set<String> expectedSignatures = createSetOfSignatures(expectedSignatureValues);
         verifySignaturesContainExpectedValues(signatures, expectedSignatures);
         assertEquals("The returned signature scheme is not the expected value", schemeVersion,
-                mPackageSetting.signatures.mSigningDetails.signatureSchemeVersion);
+                mPackageSetting.signatures.mSigningDetails.getSignatureSchemeVersion());
         for (Signature signature : signatures) {
             String signatureValue = HexDump.toHexString(signature.toByteArray(), false);
             int expectedCapabilities = SIGNATURE_TO_CAPABILITY_MAP.get(signatureValue);
diff --git a/services/tests/servicestests/src/com/android/server/pm/backup/BackupUtilsTest.java b/services/tests/servicestests/src/com/android/server/pm/backup/BackupUtilsTest.java
index 182760b..b447857 100644
--- a/services/tests/servicestests/src/com/android/server/pm/backup/BackupUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/backup/BackupUtilsTest.java
@@ -25,8 +25,8 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManagerInternal;
-import android.content.pm.PackageParser;
 import android.content.pm.Signature;
+import android.content.pm.SigningDetails;
 import android.content.pm.SigningInfo;
 import android.platform.test.annotations.Presubmit;
 import android.test.MoreAsserts;
@@ -95,9 +95,9 @@
         PackageInfo packageInfo = new PackageInfo();
         packageInfo.packageName = "test";
         packageInfo.signingInfo = new SigningInfo(
-                new PackageParser.SigningDetails(
+                new SigningDetails(
                         new Signature[] {SIGNATURE_1},
-                        PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
+                        SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
                         null,
                         null));
         packageInfo.applicationInfo = new ApplicationInfo();
@@ -114,9 +114,9 @@
         PackageInfo packageInfo = new PackageInfo();
         packageInfo.packageName = "test";
         packageInfo.signingInfo = new SigningInfo(
-                new PackageParser.SigningDetails(
+                new SigningDetails(
                         new Signature[] {SIGNATURE_1},
-                        PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
+                        SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
                         null,
                         null));
         packageInfo.applicationInfo = new ApplicationInfo();
@@ -197,9 +197,9 @@
         PackageInfo packageInfo = new PackageInfo();
         packageInfo.packageName = "test";
         packageInfo.signingInfo = new SigningInfo(
-                new PackageParser.SigningDetails(
+                new SigningDetails(
                         new Signature[] {SIGNATURE_1, SIGNATURE_2, SIGNATURE_3},
-                        PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
+                        SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
                         null,
                         null));
         packageInfo.applicationInfo = new ApplicationInfo();
@@ -219,9 +219,9 @@
         PackageInfo packageInfo = new PackageInfo();
         packageInfo.packageName = "test";
         packageInfo.signingInfo = new SigningInfo(
-                new PackageParser.SigningDetails(
+                new SigningDetails(
                         new Signature[] {SIGNATURE_1, SIGNATURE_2, SIGNATURE_3},
-                        PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
+                        SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
                         null,
                         null));
         packageInfo.applicationInfo = new ApplicationInfo();
@@ -240,9 +240,9 @@
         PackageInfo packageInfo = new PackageInfo();
         packageInfo.packageName = "test";
         packageInfo.signingInfo = new SigningInfo(
-                new PackageParser.SigningDetails(
+                new SigningDetails(
                         new Signature[] {SIGNATURE_1, SIGNATURE_2},
-                        PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
+                        SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
                         null,
                         null));
         packageInfo.applicationInfo = new ApplicationInfo();
@@ -262,9 +262,9 @@
         PackageInfo packageInfo = new PackageInfo();
         packageInfo.packageName = "test";
         packageInfo.signingInfo = new SigningInfo(
-                new PackageParser.SigningDetails(
+                new SigningDetails(
                         new Signature[] {SIGNATURE_1, SIGNATURE_2, SIGNATURE_3},
-                        PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
+                        SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
                         null,
                         null));
         packageInfo.applicationInfo = new ApplicationInfo();
@@ -285,9 +285,9 @@
         PackageInfo packageInfo = new PackageInfo();
         packageInfo.packageName = "test";
         packageInfo.signingInfo = new SigningInfo(
-                new PackageParser.SigningDetails(
+                new SigningDetails(
                         new Signature[] {SIGNATURE_1},
-                        PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
+                        SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
                         null,
                         null));
         packageInfo.applicationInfo = new ApplicationInfo();
@@ -309,9 +309,9 @@
         PackageInfo packageInfo = new PackageInfo();
         packageInfo.packageName = "test";
         packageInfo.signingInfo = new SigningInfo(
-                new PackageParser.SigningDetails(
+                new SigningDetails(
                         new Signature[] {SIGNATURE_1, SIGNATURE_2},
-                        PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
+                        SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
                         null,
                         null));
         packageInfo.applicationInfo = new ApplicationInfo();
@@ -336,9 +336,9 @@
         PackageInfo packageInfo = new PackageInfo();
         packageInfo.packageName = "test";
         packageInfo.signingInfo = new SigningInfo(
-                new PackageParser.SigningDetails(
+                new SigningDetails(
                         new Signature[] {SIGNATURE_1, SIGNATURE_2},
-                        PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
+                        SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
                         null,
                         null));
         packageInfo.applicationInfo = new ApplicationInfo();
diff --git a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundHw2CompatTest.java b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundHw2CompatTest.java
new file mode 100644
index 0000000..1947481
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundHw2CompatTest.java
@@ -0,0 +1,1054 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.soundtrigger_middleware;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.atMost;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.media.soundtrigger.ModelParameterRange;
+import android.media.soundtrigger.PhraseRecognitionEvent;
+import android.media.soundtrigger.Properties;
+import android.media.soundtrigger.RecognitionConfig;
+import android.media.soundtrigger.RecognitionEvent;
+import android.media.soundtrigger.RecognitionStatus;
+import android.media.soundtrigger.Status;
+import android.os.HwParcel;
+import android.os.IBinder;
+import android.os.IHwBinder;
+import android.os.IHwInterface;
+import android.os.RemoteException;
+import android.system.OsConstants;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.mockito.ArgumentCaptor;
+
+import java.util.LinkedList;
+import java.util.List;
+
+@RunWith(Parameterized.class)
+public class SoundHw2CompatTest {
+    @Parameterized.Parameter(0) public String mVersion;
+    @Parameterized.Parameter(1) public boolean mSupportConcurrentCapture;
+
+    private final Runnable mRebootRunnable = mock(Runnable.class);
+    private ISoundTriggerHal mCanonical;
+    private CaptureStateNotifier mCaptureStateNotifier;
+    private android.hardware.soundtrigger.V2_0.ISoundTriggerHw mHalDriver;
+
+    // We run the test once for every version of the underlying driver.
+    @Parameterized.Parameters(name = "{0}, concurrent={1}")
+    public static Iterable<Object[]> data() {
+        List<Object[]> result = new LinkedList<>();
+
+        for (String version : new String[]{"V2_0", "V2_1", "V2_2", "V2_3", "V2_4",}) {
+            for (boolean concurrentCapture : new boolean[]{false, true}) {
+                result.add(new Object[]{version, concurrentCapture});
+            }
+        }
+
+        return result;
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        mHalDriver = (android.hardware.soundtrigger.V2_0.ISoundTriggerHw) mock(Class.forName(
+                String.format("android.hardware.soundtrigger.%s.ISoundTriggerHw", mVersion)));
+
+        clearInvocations(mRebootRunnable);
+
+        // This binder is associated with the mock, so it can be cast to either version of the
+        // HAL interface.
+        final IHwBinder binder = new IHwBinder() {
+            @Override
+            public void transact(int code, HwParcel request, HwParcel reply, int flags)
+                    throws RemoteException {
+                // This is a little hacky, but a very easy way to gracefully reject a request for
+                // an unsupported interface (after queryLocalInterface() returns null, the client
+                // will attempt a remote transaction to obtain the interface. RemoteException will
+                // cause it to give up).
+                throw new RemoteException();
+            }
+
+            @Override
+            public IHwInterface queryLocalInterface(String descriptor) {
+                if (descriptor.equals("android.hardware.soundtrigger@2.0::ISoundTriggerHw")
+                        || descriptor.equals("android.hardware.soundtrigger@2.1::ISoundTriggerHw")
+                        && mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw
+                        || descriptor.equals("android.hardware.soundtrigger@2.2::ISoundTriggerHw")
+                        && mHalDriver instanceof android.hardware.soundtrigger.V2_2.ISoundTriggerHw
+                        || descriptor.equals("android.hardware.soundtrigger@2.3::ISoundTriggerHw")
+                        && mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw
+                        || descriptor.equals("android.hardware.soundtrigger@2.4::ISoundTriggerHw")
+                        && mHalDriver instanceof android.hardware.soundtrigger.V2_4.ISoundTriggerHw) {
+                    return mHalDriver;
+                }
+                return null;
+            }
+
+            @Override
+            public boolean linkToDeath(DeathRecipient recipient, long cookie) {
+                try {
+                    return mHalDriver.linkToDeath(recipient, cookie);
+                } catch (RemoteException e) {
+                    throw e.rethrowAsRuntimeException();
+                }
+            }
+
+            @Override
+            public boolean unlinkToDeath(DeathRecipient recipient) {
+                try {
+                    return mHalDriver.unlinkToDeath(recipient);
+                } catch (RemoteException e) {
+                    throw e.rethrowAsRuntimeException();
+                }
+            }
+        };
+        when(mHalDriver.asBinder()).thenReturn(binder);
+
+        android.hardware.soundtrigger.V2_3.Properties halProperties =
+                TestUtil.createDefaultProperties_2_3(mSupportConcurrentCapture);
+        doAnswer(invocation -> {
+            ((android.hardware.soundtrigger.V2_0.ISoundTriggerHw.getPropertiesCallback) invocation.getArgument(
+                    0)).onValues(0, halProperties.base);
+            return null;
+        }).when(mHalDriver).getProperties(any());
+
+        if (mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw) {
+            android.hardware.soundtrigger.V2_3.ISoundTriggerHw driver =
+                    (android.hardware.soundtrigger.V2_3.ISoundTriggerHw) mHalDriver;
+            doAnswer(invocation -> {
+                ((android.hardware.soundtrigger.V2_3.ISoundTriggerHw.getProperties_2_3Callback) invocation.getArgument(
+                        0)).onValues(0, halProperties);
+                return null;
+            }).when(driver).getProperties_2_3(any());
+        }
+
+        mCaptureStateNotifier = spy(new CaptureStateNotifier());
+
+        mCanonical = SoundTriggerHw2Compat.create(mHalDriver, mRebootRunnable,
+                mCaptureStateNotifier);
+
+        // During initialization any method can be called, but after we're starting to enforce that
+        // no additional methods are called.
+        clearInvocations(mHalDriver);
+    }
+
+    @After
+    public void tearDown() {
+        mCanonical.detach();
+        verifyNoMoreInteractions(mHalDriver);
+        verifyNoMoreInteractions(mRebootRunnable);
+        mCaptureStateNotifier.verifyNoMoreListeners();
+    }
+
+    @Test
+    public void testSetUpAndTearDown() {
+    }
+
+    @Test
+    public void testReboot() {
+        mCanonical.reboot();
+        verify(mRebootRunnable).run();
+    }
+
+    @Test
+    public void testGetProperties() throws Exception {
+        Properties properties = mCanonical.getProperties();
+
+        if (mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw) {
+            android.hardware.soundtrigger.V2_3.ISoundTriggerHw driver =
+                    (android.hardware.soundtrigger.V2_3.ISoundTriggerHw) mHalDriver;
+            // It is OK for the SUT to cache the properties, so the underlying method doesn't
+            // need to be called every single time.
+            verify(driver, atMost(1)).getProperties_2_3(any());
+            TestUtil.validateDefaultProperties(properties, mSupportConcurrentCapture);
+        } else {
+            // It is OK for the SUT to cache the properties, so the underlying method doesn't
+            // need to be called every single time.
+            verify(mHalDriver, atMost(1)).getProperties(any());
+            TestUtil.validateDefaultProperties(properties, mSupportConcurrentCapture, 0, "");
+        }
+    }
+
+    private int loadGenericModel_2_0(ISoundTriggerHal.ModelCallback canonicalCallback)
+            throws Exception {
+        final int handle = 29;
+        ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHw.SoundModel> modelCaptor =
+                ArgumentCaptor.forClass(
+                        android.hardware.soundtrigger.V2_0.ISoundTriggerHw.SoundModel.class);
+        ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback> callbackCaptor =
+                ArgumentCaptor.forClass(
+                        android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.class);
+
+        doAnswer(invocation -> {
+            android.hardware.soundtrigger.V2_0.ISoundTriggerHw.loadSoundModelCallback
+                    resultCallback = invocation.getArgument(3);
+
+            // This is the return of this method.
+            resultCallback.onValues(0, handle);
+            return null;
+        }).when(mHalDriver).loadSoundModel(any(), any(), anyInt(), any());
+
+        assertEquals(handle,
+                mCanonical.loadSoundModel(TestUtil.createGenericSoundModel(), canonicalCallback));
+
+        verify(mHalDriver).loadSoundModel(modelCaptor.capture(), callbackCaptor.capture(), anyInt(),
+                any());
+
+        TestUtil.validateGenericSoundModel_2_0(modelCaptor.getValue());
+        validateCallback_2_0(callbackCaptor.getValue(), canonicalCallback);
+        return handle;
+    }
+
+    private int loadGenericModel_2_1(ISoundTriggerHal.ModelCallback canonicalCallback)
+            throws Exception {
+        final android.hardware.soundtrigger.V2_1.ISoundTriggerHw driver_2_1 =
+                (android.hardware.soundtrigger.V2_1.ISoundTriggerHw) mHalDriver;
+
+        final int handle = 29;
+        ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel> modelCaptor =
+                ArgumentCaptor.forClass(
+                        android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel.class);
+        ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback> callbackCaptor =
+                ArgumentCaptor.forClass(
+                        android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.class);
+
+        doAnswer(invocation -> {
+            android.hardware.soundtrigger.V2_1.ISoundTriggerHw.loadSoundModel_2_1Callback
+                    resultCallback = invocation.getArgument(3);
+
+            // This is the return of this method.
+            resultCallback.onValues(0, handle);
+            return null;
+        }).when(driver_2_1).loadSoundModel_2_1(any(), any(), anyInt(), any());
+
+        assertEquals(handle,
+                mCanonical.loadSoundModel(TestUtil.createGenericSoundModel(), canonicalCallback));
+
+        verify(driver_2_1).loadSoundModel_2_1(modelCaptor.capture(), callbackCaptor.capture(),
+                anyInt(), any());
+
+        TestUtil.validateGenericSoundModel_2_1(modelCaptor.getValue());
+        validateCallback_2_1(callbackCaptor.getValue(), canonicalCallback);
+        return handle;
+    }
+
+    private int loadGenericModel_2_4(ISoundTriggerHal.ModelCallback canonicalCallback)
+            throws Exception {
+        final android.hardware.soundtrigger.V2_4.ISoundTriggerHw driver_2_4 =
+                (android.hardware.soundtrigger.V2_4.ISoundTriggerHw) mHalDriver;
+
+        final int handle = 29;
+        ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel> modelCaptor =
+                ArgumentCaptor.forClass(
+                        android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel.class);
+        ArgumentCaptor<android.hardware.soundtrigger.V2_4.ISoundTriggerHwCallback> callbackCaptor =
+                ArgumentCaptor.forClass(
+                        android.hardware.soundtrigger.V2_4.ISoundTriggerHwCallback.class);
+
+        doAnswer(invocation -> {
+            android.hardware.soundtrigger.V2_4.ISoundTriggerHw.loadSoundModel_2_4Callback
+                    resultCallback = invocation.getArgument(2);
+
+            // This is the return of this method.
+            resultCallback.onValues(0, handle);
+            return null;
+        }).when(driver_2_4).loadSoundModel_2_4(any(), any(), any());
+
+        assertEquals(handle,
+                mCanonical.loadSoundModel(TestUtil.createGenericSoundModel(), canonicalCallback));
+
+        verify(driver_2_4).loadSoundModel_2_4(modelCaptor.capture(), callbackCaptor.capture(),
+                any());
+
+        TestUtil.validateGenericSoundModel_2_1(modelCaptor.getValue());
+        validateCallback_2_4(callbackCaptor.getValue(), canonicalCallback);
+        return handle;
+    }
+
+    private int loadGenericModel(ISoundTriggerHal.ModelCallback canonicalCallback)
+            throws Exception {
+        if (mHalDriver instanceof android.hardware.soundtrigger.V2_4.ISoundTriggerHw) {
+            return loadGenericModel_2_4(canonicalCallback);
+        } else if (mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw) {
+            return loadGenericModel_2_1(canonicalCallback);
+        } else {
+            return loadGenericModel_2_0(canonicalCallback);
+        }
+    }
+
+    @Test
+    public void testLoadGenericModel() throws Exception {
+        ISoundTriggerHal.ModelCallback canonicalCallback = mock(
+                ISoundTriggerHal.ModelCallback.class);
+        loadGenericModel(canonicalCallback);
+    }
+
+    @Test
+    public void testMaxModels() throws Exception {
+        assumeFalse(mHalDriver instanceof android.hardware.soundtrigger.V2_4.ISoundTriggerHw);
+
+        // Register global callback.
+        ISoundTriggerHal.GlobalCallback globalCallback = mock(
+                ISoundTriggerHal.GlobalCallback.class);
+        mCanonical.registerCallback(globalCallback);
+
+        ISoundTriggerHal.ModelCallback canonicalCallback = mock(
+                ISoundTriggerHal.ModelCallback.class);
+        final int maxModels = TestUtil.createDefaultProperties_2_0(false).maxSoundModels;
+        int[] modelHandles = new int[maxModels];
+
+        // Load as many models as we're allowed.
+        for (int i = 0; i < maxModels; ++i) {
+            modelHandles[i] = loadGenericModel(canonicalCallback);
+            verifyNoMoreInteractions(mHalDriver);
+            clearInvocations(mHalDriver);
+        }
+
+        // Now try to load an additional one and expect failure without invoking the underlying
+        // driver.
+        try {
+            mCanonical.loadPhraseSoundModel(TestUtil.createPhraseSoundModel(), canonicalCallback);
+            fail("Expected an exception");
+        } catch (RecoverableException e) {
+            assertEquals(Status.RESOURCE_CONTENTION, e.errorCode);
+        }
+
+        // Unload a single model and expect a onResourcesAvailable().
+        mCanonical.unloadSoundModel(modelHandles[0]);
+        verify(mHalDriver).unloadSoundModel(modelHandles[0]);
+
+        mCanonical.flushCallbacks();
+        verify(globalCallback).onResourcesAvailable();
+    }
+
+    private void testLoadGenericModelBusy_2_4() throws Exception {
+        final android.hardware.soundtrigger.V2_4.ISoundTriggerHw driver_2_4 =
+                (android.hardware.soundtrigger.V2_4.ISoundTriggerHw) mHalDriver;
+
+        doAnswer(invocation -> {
+            android.hardware.soundtrigger.V2_4.ISoundTriggerHw.loadSoundModel_2_4Callback
+                    resultCallback = invocation.getArgument(2);
+
+            // This is the return of this method.
+            resultCallback.onValues(-OsConstants.EBUSY, 0);
+            return null;
+        }).when(driver_2_4).loadSoundModel_2_4(any(), any(), any());
+
+        ISoundTriggerHal.ModelCallback canonicalCallback = mock(
+                ISoundTriggerHal.ModelCallback.class);
+        try {
+            mCanonical.loadSoundModel(TestUtil.createGenericSoundModel(), canonicalCallback);
+            fail("Expected an exception");
+        } catch (RecoverableException e) {
+            assertEquals(Status.RESOURCE_CONTENTION, e.errorCode);
+        }
+        verify(driver_2_4).loadSoundModel_2_4(any(), any(), any());
+    }
+
+    @Test
+    public void testLoadGenericModelBusy() throws Exception {
+        if (mHalDriver instanceof android.hardware.soundtrigger.V2_4.ISoundTriggerHw) {
+            testLoadGenericModelBusy_2_4();
+        }
+    }
+
+    private int loadPhraseModel_2_0(ISoundTriggerHal.ModelCallback canonicalCallback)
+            throws Exception {
+        final int handle = 29;
+        ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHw.PhraseSoundModel>
+                modelCaptor = ArgumentCaptor.forClass(
+                android.hardware.soundtrigger.V2_0.ISoundTriggerHw.PhraseSoundModel.class);
+        ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback> callbackCaptor =
+                ArgumentCaptor.forClass(
+                        android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.class);
+
+        doAnswer(invocation -> {
+            android.hardware.soundtrigger.V2_0.ISoundTriggerHw.loadPhraseSoundModelCallback
+                    resultCallback = invocation.getArgument(3);
+
+            // This is the return of this method.
+            resultCallback.onValues(0, handle);
+            return null;
+        }).when(mHalDriver).loadPhraseSoundModel(any(), any(), anyInt(), any());
+
+        assertEquals(handle, mCanonical.loadPhraseSoundModel(TestUtil.createPhraseSoundModel(),
+                canonicalCallback));
+
+        verify(mHalDriver).loadPhraseSoundModel(modelCaptor.capture(), callbackCaptor.capture(),
+                anyInt(), any());
+
+        TestUtil.validatePhraseSoundModel_2_0(modelCaptor.getValue());
+        validateCallback_2_0(callbackCaptor.getValue(), canonicalCallback);
+        return handle;
+    }
+
+    private int loadPhraseModel_2_1(ISoundTriggerHal.ModelCallback canonicalCallback)
+            throws Exception {
+        final android.hardware.soundtrigger.V2_1.ISoundTriggerHw driver_2_1 =
+                (android.hardware.soundtrigger.V2_1.ISoundTriggerHw) mHalDriver;
+
+        final int handle = 29;
+        ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHw.PhraseSoundModel>
+                modelCaptor = ArgumentCaptor.forClass(
+                android.hardware.soundtrigger.V2_1.ISoundTriggerHw.PhraseSoundModel.class);
+        ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback> callbackCaptor =
+                ArgumentCaptor.forClass(
+                        android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.class);
+
+        doAnswer(invocation -> {
+            android.hardware.soundtrigger.V2_1.ISoundTriggerHw.loadPhraseSoundModel_2_1Callback
+                    resultCallback = invocation.getArgument(3);
+
+            // This is the return of this method.
+            resultCallback.onValues(0, handle);
+            return null;
+        }).when(driver_2_1).loadPhraseSoundModel_2_1(any(), any(), anyInt(), any());
+
+        assertEquals(handle, mCanonical.loadPhraseSoundModel(TestUtil.createPhraseSoundModel(),
+                canonicalCallback));
+
+        verify(driver_2_1).loadPhraseSoundModel_2_1(modelCaptor.capture(), callbackCaptor.capture(),
+                anyInt(), any());
+
+        TestUtil.validatePhraseSoundModel_2_1(modelCaptor.getValue());
+        validateCallback_2_1(callbackCaptor.getValue(), canonicalCallback);
+        return handle;
+    }
+
+    private int loadPhraseModel_2_4(ISoundTriggerHal.ModelCallback canonicalCallback)
+            throws Exception {
+        final android.hardware.soundtrigger.V2_4.ISoundTriggerHw driver_2_4 =
+                (android.hardware.soundtrigger.V2_4.ISoundTriggerHw) mHalDriver;
+
+        final int handle = 29;
+        ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHw.PhraseSoundModel>
+                modelCaptor = ArgumentCaptor.forClass(
+                android.hardware.soundtrigger.V2_1.ISoundTriggerHw.PhraseSoundModel.class);
+        ArgumentCaptor<android.hardware.soundtrigger.V2_4.ISoundTriggerHwCallback> callbackCaptor =
+                ArgumentCaptor.forClass(
+                        android.hardware.soundtrigger.V2_4.ISoundTriggerHwCallback.class);
+
+        doAnswer(invocation -> {
+            android.hardware.soundtrigger.V2_4.ISoundTriggerHw.loadPhraseSoundModel_2_4Callback
+                    resultCallback = invocation.getArgument(2);
+
+            // This is the return of this method.
+            resultCallback.onValues(0, handle);
+            return null;
+        }).when(driver_2_4).loadPhraseSoundModel_2_4(any(), any(), any());
+
+        assertEquals(handle, mCanonical.loadPhraseSoundModel(TestUtil.createPhraseSoundModel(),
+                canonicalCallback));
+
+        verify(driver_2_4).loadPhraseSoundModel_2_4(modelCaptor.capture(), callbackCaptor.capture(),
+                any());
+
+        TestUtil.validatePhraseSoundModel_2_1(modelCaptor.getValue());
+        validateCallback_2_4(callbackCaptor.getValue(), canonicalCallback);
+        return handle;
+    }
+
+    public int loadPhraseModel(ISoundTriggerHal.ModelCallback canonicalCallback) throws Exception {
+        if (mHalDriver instanceof android.hardware.soundtrigger.V2_4.ISoundTriggerHw) {
+            return loadPhraseModel_2_4(canonicalCallback);
+        } else if (mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw) {
+            return loadPhraseModel_2_1(canonicalCallback);
+        } else {
+            return loadPhraseModel_2_0(canonicalCallback);
+        }
+    }
+
+    @Test
+    public void testLoadPhraseModel() throws Exception {
+        ISoundTriggerHal.ModelCallback canonicalCallback = mock(
+                ISoundTriggerHal.ModelCallback.class);
+        loadPhraseModel(canonicalCallback);
+    }
+
+    private void testLoadPhraseModelBusy_2_4() throws Exception {
+        final android.hardware.soundtrigger.V2_4.ISoundTriggerHw driver_2_4 =
+                (android.hardware.soundtrigger.V2_4.ISoundTriggerHw) mHalDriver;
+
+        doAnswer(invocation -> {
+            android.hardware.soundtrigger.V2_4.ISoundTriggerHw.loadPhraseSoundModel_2_4Callback
+                    resultCallback = invocation.getArgument(2);
+
+            // This is the return of this method.
+            resultCallback.onValues(-OsConstants.EBUSY, 0);
+            return null;
+        }).when(driver_2_4).loadPhraseSoundModel_2_4(any(), any(), any());
+
+        ISoundTriggerHal.ModelCallback canonicalCallback = mock(
+                ISoundTriggerHal.ModelCallback.class);
+        try {
+            mCanonical.loadPhraseSoundModel(TestUtil.createPhraseSoundModel(), canonicalCallback);
+            fail("Expected an exception");
+        } catch (RecoverableException e) {
+            assertEquals(Status.RESOURCE_CONTENTION, e.errorCode);
+        }
+        verify(driver_2_4).loadPhraseSoundModel_2_4(any(), any(), any());
+    }
+
+    @Test
+    public void testLoadPhraseModelBusy() throws Exception {
+        if (mHalDriver instanceof android.hardware.soundtrigger.V2_4.ISoundTriggerHw) {
+            testLoadPhraseModelBusy_2_4();
+        }
+    }
+
+    @Test
+    public void testUnloadModel() throws Exception {
+        mCanonical.unloadSoundModel(14);
+        verify(mHalDriver).unloadSoundModel(14);
+    }
+
+    private void startRecognition_2_0(int handle, ISoundTriggerHal.ModelCallback canonicalCallback)
+            throws Exception {
+        ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig>
+                configCaptor = ArgumentCaptor.forClass(
+                android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig.class);
+        ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback> callbackCaptor =
+                ArgumentCaptor.forClass(
+                        android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.class);
+
+        when(mHalDriver.startRecognition(eq(handle), any(), any(), anyInt())).thenReturn(0);
+
+        RecognitionConfig config = TestUtil.createRecognitionConfig();
+        mCanonical.startRecognition(handle, 203, 204, config);
+        verify(mHalDriver).startRecognition(eq(handle), configCaptor.capture(),
+                callbackCaptor.capture(), anyInt());
+
+        TestUtil.validateRecognitionConfig_2_0(configCaptor.getValue(), 203, 204);
+        validateCallback_2_0(callbackCaptor.getValue(), canonicalCallback);
+    }
+
+    private void startRecognition_2_1(int handle, ISoundTriggerHal.ModelCallback canonicalCallback)
+            throws Exception {
+        final android.hardware.soundtrigger.V2_1.ISoundTriggerHw driver_2_1 =
+                (android.hardware.soundtrigger.V2_1.ISoundTriggerHw) mHalDriver;
+
+        ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig>
+                configCaptor = ArgumentCaptor.forClass(
+                android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig.class);
+        ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback> callbackCaptor =
+                ArgumentCaptor.forClass(
+                        android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.class);
+
+        when(driver_2_1.startRecognition_2_1(eq(handle), any(), any(), anyInt())).thenReturn(0);
+
+        RecognitionConfig config = TestUtil.createRecognitionConfig();
+        mCanonical.startRecognition(handle, 505, 506, config);
+        verify(driver_2_1).startRecognition_2_1(eq(handle), configCaptor.capture(),
+                callbackCaptor.capture(), anyInt());
+
+        TestUtil.validateRecognitionConfig_2_1(configCaptor.getValue(), 505, 506);
+        validateCallback_2_1(callbackCaptor.getValue(), canonicalCallback);
+    }
+
+    private void startRecognition_2_3(int handle) throws Exception {
+        final android.hardware.soundtrigger.V2_3.ISoundTriggerHw driver_2_3 =
+                (android.hardware.soundtrigger.V2_3.ISoundTriggerHw) mHalDriver;
+        ArgumentCaptor<android.hardware.soundtrigger.V2_3.RecognitionConfig> configCaptor =
+                ArgumentCaptor.forClass(android.hardware.soundtrigger.V2_3.RecognitionConfig.class);
+
+        when(driver_2_3.startRecognition_2_3(eq(handle), any())).thenReturn(0);
+
+        RecognitionConfig config = TestUtil.createRecognitionConfig();
+        mCanonical.startRecognition(handle, 808, 909, config);
+        verify(driver_2_3).startRecognition_2_3(eq(handle), configCaptor.capture());
+        TestUtil.validateRecognitionConfig_2_3(configCaptor.getValue(), 808, 909);
+    }
+
+    private void startRecognition_2_4(int handle) throws Exception {
+        final android.hardware.soundtrigger.V2_4.ISoundTriggerHw driver_2_4 =
+                (android.hardware.soundtrigger.V2_4.ISoundTriggerHw) mHalDriver;
+        ArgumentCaptor<android.hardware.soundtrigger.V2_3.RecognitionConfig> configCaptor =
+                ArgumentCaptor.forClass(android.hardware.soundtrigger.V2_3.RecognitionConfig.class);
+
+        when(driver_2_4.startRecognition_2_4(eq(handle), any())).thenReturn(0);
+
+        RecognitionConfig config = TestUtil.createRecognitionConfig();
+        mCanonical.startRecognition(handle, 21, 22, config);
+        verify(driver_2_4).startRecognition_2_4(eq(handle), configCaptor.capture());
+        TestUtil.validateRecognitionConfig_2_3(configCaptor.getValue(), 21, 22);
+    }
+
+    private void startRecognition(int handle, ISoundTriggerHal.ModelCallback canonicalCallback)
+            throws Exception {
+        if (mHalDriver instanceof android.hardware.soundtrigger.V2_4.ISoundTriggerHw) {
+            startRecognition_2_4(handle);
+        } else if (mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw) {
+            startRecognition_2_3(handle);
+        } else if (mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw) {
+            startRecognition_2_1(handle, canonicalCallback);
+        } else {
+            startRecognition_2_0(handle, canonicalCallback);
+        }
+    }
+
+    @Test
+    public void testStartRecognition() throws Exception {
+        // First load.
+        ISoundTriggerHal.ModelCallback canonicalCallback = mock(
+                ISoundTriggerHal.ModelCallback.class);
+        final int handle = loadGenericModel(canonicalCallback);
+
+        // Then start.
+        startRecognition(handle, canonicalCallback);
+    }
+
+    private void testStartRecognitionBusy_2_4() throws Exception {
+        final android.hardware.soundtrigger.V2_4.ISoundTriggerHw driver_2_4 =
+                (android.hardware.soundtrigger.V2_4.ISoundTriggerHw) mHalDriver;
+
+        final int handle = 68;
+        when(driver_2_4.startRecognition_2_4(eq(handle), any())).thenReturn(-OsConstants.EBUSY);
+
+        RecognitionConfig config = TestUtil.createRecognitionConfig();
+        try {
+            mCanonical.startRecognition(handle, 34, 35, config);
+            fail("Expected an exception");
+        } catch (RecoverableException e) {
+            assertEquals(Status.RESOURCE_CONTENTION, e.errorCode);
+        }
+        verify(driver_2_4).startRecognition_2_4(eq(handle), any());
+    }
+
+    @Test
+    public void testStartRecognitionBusy() throws Exception {
+        if (mHalDriver instanceof android.hardware.soundtrigger.V2_4.ISoundTriggerHw) {
+            testStartRecognitionBusy_2_4();
+        }
+    }
+
+    @Test
+    public void testNoRegisterCaptureStateListener() {
+        assumeTrue(mHalDriver instanceof android.hardware.soundtrigger.V2_4.ISoundTriggerHw
+                || mSupportConcurrentCapture);
+        verify(mCaptureStateNotifier, never()).registerListener(any());
+    }
+
+    @Test
+    public void testConcurrentCaptureAbort() throws Exception {
+        assumeFalse(mHalDriver instanceof android.hardware.soundtrigger.V2_4.ISoundTriggerHw
+                || mSupportConcurrentCapture);
+        verify(mCaptureStateNotifier, atLeast(1)).registerListener(any());
+
+        // Register global callback.
+        ISoundTriggerHal.GlobalCallback globalCallback = mock(
+                ISoundTriggerHal.GlobalCallback.class);
+        mCanonical.registerCallback(globalCallback);
+
+        // Load.
+        ISoundTriggerHal.ModelCallback canonicalCallback = mock(
+                ISoundTriggerHal.ModelCallback.class);
+        final int handle = loadGenericModel(canonicalCallback);
+
+        // Then start.
+        startRecognition(handle, canonicalCallback);
+
+        // Now activate external capture.
+        mCaptureStateNotifier.setState(true);
+
+        // Expect hardware to have been stopped.
+        verify(mHalDriver).stopRecognition(handle);
+
+        // Expect an abort event (async).
+        ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
+                RecognitionEvent.class);
+        mCanonical.flushCallbacks();
+        verify(canonicalCallback).recognitionCallback(eq(handle), eventCaptor.capture());
+        assertEquals(RecognitionStatus.ABORTED, eventCaptor.getValue().status);
+
+        // Deactivate external capture.
+        mCaptureStateNotifier.setState(false);
+
+        // Expect a onResourcesAvailable().
+        mCanonical.flushCallbacks();
+        verify(globalCallback).onResourcesAvailable();
+    }
+
+    @Test
+    public void testConcurrentCaptureReject() throws Exception {
+        assumeFalse(mHalDriver instanceof android.hardware.soundtrigger.V2_4.ISoundTriggerHw
+                || mSupportConcurrentCapture);
+        verify(mCaptureStateNotifier, atLeast(1)).registerListener(any());
+
+        // Register global callback.
+        ISoundTriggerHal.GlobalCallback globalCallback = mock(
+                ISoundTriggerHal.GlobalCallback.class);
+        mCanonical.registerCallback(globalCallback);
+
+        // Load (this registers the callback).
+        ISoundTriggerHal.ModelCallback canonicalCallback = mock(
+                ISoundTriggerHal.ModelCallback.class);
+        final int handle = loadGenericModel(canonicalCallback);
+
+        // Report external capture active.
+        mCaptureStateNotifier.setState(true);
+
+        // Then start.
+        RecognitionConfig config = TestUtil.createRecognitionConfig();
+        try {
+            mCanonical.startRecognition(handle, 203, 204, config);
+            fail("Expected an exception");
+        } catch (RecoverableException e) {
+            assertEquals(Status.RESOURCE_CONTENTION, e.errorCode);
+        }
+
+        // Deactivate external capture.
+        mCaptureStateNotifier.setState(false);
+
+        // Expect a onResourcesAvailable().
+        mCanonical.flushCallbacks();
+        verify(globalCallback).onResourcesAvailable();
+    }
+
+    @Test
+    public void testStopRecognition() throws Exception {
+        mCanonical.stopRecognition(17);
+        verify(mHalDriver).stopRecognition(17);
+    }
+
+    @Test
+    public void testForceRecognition() throws Exception {
+        if (mHalDriver instanceof android.hardware.soundtrigger.V2_2.ISoundTriggerHw) {
+            android.hardware.soundtrigger.V2_2.ISoundTriggerHw driver_2_2 =
+                    (android.hardware.soundtrigger.V2_2.ISoundTriggerHw) mHalDriver;
+            mCanonical.forceRecognitionEvent(14);
+            verify(driver_2_2).getModelState(14);
+        } else {
+            try {
+                mCanonical.forceRecognitionEvent(14);
+                fail("Expected an exception");
+            } catch (RecoverableException e) {
+                assertEquals(Status.OPERATION_NOT_SUPPORTED, e.errorCode);
+            }
+        }
+    }
+
+    @Test
+    public void testGetParameter() throws Exception {
+        assumeTrue(mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw);
+
+        android.hardware.soundtrigger.V2_3.ISoundTriggerHw driver_2_3 =
+                (android.hardware.soundtrigger.V2_3.ISoundTriggerHw) mHalDriver;
+
+        doAnswer(invocation -> {
+            android.hardware.soundtrigger.V2_3.ISoundTriggerHw.getParameterCallback resultCallback =
+                    invocation.getArgument(2);
+
+            // This is the return of this method.
+            resultCallback.onValues(0, 99);
+            return null;
+        }).when(driver_2_3).getParameter(eq(21), eq(47), any());
+
+        assertEquals(99, mCanonical.getModelParameter(21, 47));
+        verify(driver_2_3).getParameter(eq(21), eq(47), any());
+    }
+
+    @Test
+    public void testSetParameter() throws Exception {
+        assumeTrue(mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw);
+
+        android.hardware.soundtrigger.V2_3.ISoundTriggerHw driver_2_3 =
+                (android.hardware.soundtrigger.V2_3.ISoundTriggerHw) mHalDriver;
+
+        mCanonical.setModelParameter(212, 247, 80);
+        verify(driver_2_3).setParameter(212, 247, 80);
+    }
+
+    @Test
+    public void testQueryParameterSupported() throws Exception {
+        assumeTrue(mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw);
+
+        android.hardware.soundtrigger.V2_3.ISoundTriggerHw driver_2_3 =
+                (android.hardware.soundtrigger.V2_3.ISoundTriggerHw) mHalDriver;
+
+        doAnswer(invocation -> {
+            android.hardware.soundtrigger.V2_3.ISoundTriggerHw.queryParameterCallback
+                    resultCallback = invocation.getArgument(2);
+
+            // This is the return of this method.
+            android.hardware.soundtrigger.V2_3.ModelParameterRange range =
+                    new android.hardware.soundtrigger.V2_3.ModelParameterRange();
+            range.start = 34;
+            range.end = 45;
+            android.hardware.soundtrigger.V2_3.OptionalModelParameterRange optionalRange =
+                    new android.hardware.soundtrigger.V2_3.OptionalModelParameterRange();
+            optionalRange.range(range);
+            resultCallback.onValues(0, optionalRange);
+            return null;
+        }).when(driver_2_3).queryParameter(eq(11), eq(12), any());
+
+        ModelParameterRange range = mCanonical.queryParameter(11, 12);
+        assertNotNull(range);
+        assertEquals(34, range.minInclusive);
+        assertEquals(45, range.maxInclusive);
+        verify(driver_2_3).queryParameter(eq(11), eq(12), any());
+    }
+
+    @Test
+    public void testQueryParameterNotSupported() throws Exception {
+        if (mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw) {
+            android.hardware.soundtrigger.V2_3.ISoundTriggerHw driver_2_3 =
+                    (android.hardware.soundtrigger.V2_3.ISoundTriggerHw) mHalDriver;
+
+            doAnswer(invocation -> {
+                android.hardware.soundtrigger.V2_3.ISoundTriggerHw.queryParameterCallback
+                        resultCallback = invocation.getArgument(2);
+
+                // This is the return of this method.
+                android.hardware.soundtrigger.V2_3.OptionalModelParameterRange optionalRange =
+                        new android.hardware.soundtrigger.V2_3.OptionalModelParameterRange();
+                resultCallback.onValues(0, optionalRange);
+                return null;
+            }).when(driver_2_3).queryParameter(eq(11), eq(12), any());
+
+            ModelParameterRange range = mCanonical.queryParameter(11, 12);
+            assertNull(range);
+            verify(driver_2_3).queryParameter(eq(11), eq(12), any());
+        } else {
+            ModelParameterRange range = mCanonical.queryParameter(11, 12);
+            assertNull(range);
+        }
+    }
+
+    private void testGlobalCallback_2_0() {
+        ISoundTriggerHal.GlobalCallback canonicalCallback = mock(
+                ISoundTriggerHal.GlobalCallback.class);
+        mCanonical.registerCallback(canonicalCallback);
+        // We just care that it doesn't throw.
+    }
+
+    private void testGlobalCallback_2_4() throws Exception {
+        android.hardware.soundtrigger.V2_4.ISoundTriggerHw driver_2_4 =
+                (android.hardware.soundtrigger.V2_4.ISoundTriggerHw) mHalDriver;
+
+        ISoundTriggerHal.GlobalCallback canonicalCallback = mock(
+                ISoundTriggerHal.GlobalCallback.class);
+        mCanonical.registerCallback(canonicalCallback);
+
+        ArgumentCaptor<android.hardware.soundtrigger.V2_4.ISoundTriggerHwGlobalCallback>
+                callbackCaptor = ArgumentCaptor.forClass(
+                android.hardware.soundtrigger.V2_4.ISoundTriggerHwGlobalCallback.class);
+        verify(driver_2_4).registerGlobalCallback(callbackCaptor.capture());
+        validateGlobalCallback_2_4(callbackCaptor.getValue(), canonicalCallback);
+    }
+
+    @Test
+    public void testGlobalCallback() throws Exception {
+        if (mHalDriver instanceof android.hardware.soundtrigger.V2_4.ISoundTriggerHw) {
+            testGlobalCallback_2_4();
+        } else {
+            testGlobalCallback_2_0();
+        }
+    }
+
+    @Test
+    public void testLinkToDeath() throws Exception {
+        IBinder.DeathRecipient canonicalRecipient = mock(IBinder.DeathRecipient.class);
+        when(mHalDriver.linkToDeath(any(), anyLong())).thenReturn(true);
+        mCanonical.linkToDeath(canonicalRecipient);
+
+        ArgumentCaptor<IHwBinder.DeathRecipient> recipientCaptor = ArgumentCaptor.forClass(
+                IHwBinder.DeathRecipient.class);
+        ArgumentCaptor<Long> cookieCaptor = ArgumentCaptor.forClass(Long.class);
+        verify(mHalDriver).linkToDeath(recipientCaptor.capture(), cookieCaptor.capture());
+
+        recipientCaptor.getValue().serviceDied(cookieCaptor.getValue());
+        mCanonical.flushCallbacks();
+        verify(canonicalRecipient).binderDied();
+
+        mCanonical.unlinkToDeath(canonicalRecipient);
+        verify(mHalDriver).unlinkToDeath(recipientCaptor.getValue());
+    }
+
+    @Test
+    public void testInterfaceDescriptor() throws Exception {
+        when(mHalDriver.interfaceDescriptor()).thenReturn("ABCD");
+        assertEquals("ABCD", mCanonical.interfaceDescriptor());
+        verify(mHalDriver).interfaceDescriptor();
+    }
+
+    private void validateGlobalCallback_2_4(
+            android.hardware.soundtrigger.V2_4.ISoundTriggerHwGlobalCallback hwCallback,
+            ISoundTriggerHal.GlobalCallback canonicalCallback) throws Exception {
+        hwCallback.onResourcesAvailable();
+        mCanonical.flushCallbacks();
+        verify(canonicalCallback).onResourcesAvailable();
+    }
+
+    private void validateCallback_2_0(
+            android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback hwCallback,
+            ISoundTriggerHal.ModelCallback canonicalCallback) throws Exception {
+        {
+            final int handle = 85;
+            final int status =
+                    android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionStatus.ABORT;
+            ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
+                    RecognitionEvent.class);
+
+            hwCallback.recognitionCallback(TestUtil.createRecognitionEvent_2_0(handle, status), 99);
+            mCanonical.flushCallbacks();
+            verify(canonicalCallback).recognitionCallback(eq(handle), eventCaptor.capture());
+            TestUtil.validateRecognitionEvent(eventCaptor.getValue(), RecognitionStatus.ABORTED);
+        }
+
+        {
+            final int handle = 92;
+            final int status =
+                    android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionStatus.SUCCESS;
+            ArgumentCaptor<PhraseRecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
+                    PhraseRecognitionEvent.class);
+
+            hwCallback.phraseRecognitionCallback(
+                    TestUtil.createPhraseRecognitionEvent_2_0(handle, status), 99);
+            mCanonical.flushCallbacks();
+            verify(canonicalCallback).phraseRecognitionCallback(eq(handle), eventCaptor.capture());
+            TestUtil.validatePhraseRecognitionEvent(eventCaptor.getValue(),
+                    RecognitionStatus.SUCCESS);
+        }
+        verifyNoMoreInteractions(canonicalCallback);
+        clearInvocations(canonicalCallback);
+    }
+
+    private void validateCallback_2_1(
+            android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback hwCallback,
+            ISoundTriggerHal.ModelCallback canonicalCallback) throws Exception {
+        {
+            final int handle = 85;
+            final int status =
+                    android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionStatus.ABORT;
+            ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
+                    RecognitionEvent.class);
+
+            hwCallback.recognitionCallback_2_1(TestUtil.createRecognitionEvent_2_1(handle, status),
+                    99);
+            mCanonical.flushCallbacks();
+            verify(canonicalCallback).recognitionCallback(eq(handle), eventCaptor.capture());
+            TestUtil.validateRecognitionEvent(eventCaptor.getValue(), RecognitionStatus.ABORTED);
+        }
+
+        {
+            final int handle = 92;
+            final int status =
+                    android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionStatus.SUCCESS;
+            ArgumentCaptor<PhraseRecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
+                    PhraseRecognitionEvent.class);
+
+            hwCallback.phraseRecognitionCallback_2_1(
+                    TestUtil.createPhraseRecognitionEvent_2_1(handle, status), 99);
+            mCanonical.flushCallbacks();
+            verify(canonicalCallback).phraseRecognitionCallback(eq(handle), eventCaptor.capture());
+            TestUtil.validatePhraseRecognitionEvent(eventCaptor.getValue(),
+                    RecognitionStatus.SUCCESS);
+        }
+        verifyNoMoreInteractions(canonicalCallback);
+        clearInvocations(canonicalCallback);
+    }
+
+    private void validateCallback_2_4(
+            android.hardware.soundtrigger.V2_4.ISoundTriggerHwCallback hwCallback,
+            ISoundTriggerHal.ModelCallback canonicalCallback) throws Exception {
+        {
+            final int handle = 85;
+            final int status =
+                    android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionStatus.ABORT;
+            ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
+                    RecognitionEvent.class);
+
+            hwCallback.recognitionCallback_2_1(TestUtil.createRecognitionEvent_2_1(handle, status),
+                    99);
+            mCanonical.flushCallbacks();
+            verify(canonicalCallback).recognitionCallback(eq(handle), eventCaptor.capture());
+            TestUtil.validateRecognitionEvent(eventCaptor.getValue(), RecognitionStatus.ABORTED);
+        }
+
+        {
+            final int handle = 92;
+            final int status =
+                    android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionStatus.SUCCESS;
+            ArgumentCaptor<PhraseRecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
+                    PhraseRecognitionEvent.class);
+
+            hwCallback.phraseRecognitionCallback_2_1(
+                    TestUtil.createPhraseRecognitionEvent_2_1(handle, status), 99);
+            mCanonical.flushCallbacks();
+            verify(canonicalCallback).phraseRecognitionCallback(eq(handle), eventCaptor.capture());
+            TestUtil.validatePhraseRecognitionEvent(eventCaptor.getValue(),
+                    RecognitionStatus.SUCCESS);
+        }
+
+        {
+            final int handle = 23;
+            hwCallback.modelUnloaded(handle);
+            mCanonical.flushCallbacks();
+            verify(canonicalCallback).modelUnloaded(handle);
+        }
+        verifyNoMoreInteractions(canonicalCallback);
+        clearInvocations(canonicalCallback);
+    }
+
+    public static class CaptureStateNotifier implements ICaptureStateNotifier {
+        private final List<Listener> mListeners = new LinkedList<>();
+
+        @Override
+        public boolean registerListener(Listener listener) {
+            mListeners.add(listener);
+            return false;
+        }
+
+        @Override
+        public void unregisterListener(Listener listener) {
+            mListeners.remove(listener);
+        }
+
+        public void setState(boolean state) {
+            for (Listener listener : mListeners) {
+                listener.onCaptureStateChange(state);
+            }
+        }
+
+        public void verifyNoMoreListeners() {
+            assertEquals(0, mListeners.size());
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
index 509eb25..1daf831 100644
--- a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
@@ -16,89 +16,50 @@
 
 package com.android.server.soundtrigger_middleware;
 
-import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.hardware.audio.common.V2_0.AudioConfig;
-import android.hardware.audio.common.V2_0.Uuid;
-import android.hardware.soundtrigger.V2_3.OptionalModelParameterRange;
-import android.media.audio.common.AudioChannelMask;
-import android.media.audio.common.AudioFormat;
-import android.media.soundtrigger_middleware.AudioCapabilities;
-import android.media.soundtrigger_middleware.ConfidenceLevel;
+import android.media.soundtrigger.ModelParameter;
+import android.media.soundtrigger.ModelParameterRange;
+import android.media.soundtrigger.PhraseRecognitionEvent;
+import android.media.soundtrigger.PhraseSoundModel;
+import android.media.soundtrigger.Properties;
+import android.media.soundtrigger.RecognitionConfig;
+import android.media.soundtrigger.RecognitionEvent;
+import android.media.soundtrigger.RecognitionStatus;
+import android.media.soundtrigger.SoundModel;
+import android.media.soundtrigger.Status;
 import android.media.soundtrigger_middleware.ISoundTriggerCallback;
 import android.media.soundtrigger_middleware.ISoundTriggerModule;
-import android.media.soundtrigger_middleware.ModelParameter;
-import android.media.soundtrigger_middleware.ModelParameterRange;
-import android.media.soundtrigger_middleware.Phrase;
-import android.media.soundtrigger_middleware.PhraseRecognitionEvent;
-import android.media.soundtrigger_middleware.PhraseRecognitionExtra;
-import android.media.soundtrigger_middleware.PhraseSoundModel;
-import android.media.soundtrigger_middleware.RecognitionConfig;
-import android.media.soundtrigger_middleware.RecognitionEvent;
-import android.media.soundtrigger_middleware.RecognitionMode;
-import android.media.soundtrigger_middleware.RecognitionStatus;
-import android.media.soundtrigger_middleware.SoundModel;
-import android.media.soundtrigger_middleware.SoundModelType;
 import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
-import android.media.soundtrigger_middleware.SoundTriggerModuleProperties;
-import android.os.HidlMemoryUtil;
-import android.os.HwParcel;
-import android.os.IHwBinder;
-import android.os.IHwInterface;
-import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
-import android.os.SharedMemory;
-import android.system.ErrnoException;
 import android.util.Pair;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
+import org.junit.runners.JUnit4;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.Mockito;
-import org.mockito.stubbing.Answer;
 
-import java.io.FileDescriptor;
-import java.nio.ByteBuffer;
-
-@RunWith(Parameterized.class)
+@RunWith(JUnit4.class)
 public class SoundTriggerMiddlewareImplTest {
-    private static final String TAG = "SoundTriggerMiddlewareImplTest";
+    @Mock public ISoundTriggerHal mHalDriver = mock(ISoundTriggerHal.class);
 
-    // We run the test once for every version of the underlying driver.
-    @Parameterized.Parameters
-    public static Object[] data() {
-        return new Object[]{
-                mock(android.hardware.soundtrigger.V2_0.ISoundTriggerHw.class),
-                mock(android.hardware.soundtrigger.V2_1.ISoundTriggerHw.class),
-                mock(android.hardware.soundtrigger.V2_2.ISoundTriggerHw.class),
-                mock(android.hardware.soundtrigger.V2_3.ISoundTriggerHw.class),
-        };
-    }
-
-    @Mock
-    @Parameterized.Parameter
-    public android.hardware.soundtrigger.V2_0.ISoundTriggerHw mHalDriver;
-
-    @Mock
-    private SoundTriggerMiddlewareImpl.AudioSessionProvider mAudioSessionProvider = mock(
-            SoundTriggerMiddlewareImpl.AudioSessionProvider.class);
+    @Mock private final SoundTriggerMiddlewareImpl.AudioSessionProvider mAudioSessionProvider =
+            mock(SoundTriggerMiddlewareImpl.AudioSessionProvider.class);
 
     private SoundTriggerMiddlewareImpl mService;
 
@@ -106,522 +67,41 @@
         return mock(ISoundTriggerCallback.Stub.class, Mockito.CALLS_REAL_METHODS);
     }
 
-    private static SoundModel createGenericSoundModel() {
-        return createSoundModel(SoundModelType.GENERIC);
-    }
-
-    private static FileDescriptor byteArrayToFileDescriptor(byte[] data) {
-        try {
-            SharedMemory shmem = SharedMemory.create("", data.length);
-            ByteBuffer buffer = shmem.mapReadWrite();
-            buffer.put(data);
-            return shmem.getFileDescriptor();
-        } catch (ErrnoException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    private static SoundModel createSoundModel(int type) {
-        SoundModel model = new SoundModel();
-        model.type = type;
-        model.uuid = "12345678-2345-3456-4567-abcdef987654";
-        model.vendorUuid = "87654321-5432-6543-7654-456789fedcba";
-        byte[] data = new byte[]{91, 92, 93, 94, 95};
-        model.data = new ParcelFileDescriptor(byteArrayToFileDescriptor(data));
-        model.dataSize = data.length;
-        return model;
-    }
-
-    private static PhraseSoundModel createPhraseSoundModel() {
-        PhraseSoundModel model = new PhraseSoundModel();
-        model.common = createSoundModel(SoundModelType.KEYPHRASE);
-        model.phrases = new Phrase[1];
-        model.phrases[0] = new Phrase();
-        model.phrases[0].id = 123;
-        model.phrases[0].users = new int[]{5, 6, 7};
-        model.phrases[0].locale = "locale";
-        model.phrases[0].text = "text";
-        model.phrases[0].recognitionModes =
-                RecognitionMode.USER_AUTHENTICATION | RecognitionMode.USER_IDENTIFICATION;
-        return model;
-    }
-
-    private static android.hardware.soundtrigger.V2_0.ISoundTriggerHw.Properties createDefaultProperties(
-            boolean supportConcurrentCapture) {
-        android.hardware.soundtrigger.V2_0.ISoundTriggerHw.Properties properties =
-                new android.hardware.soundtrigger.V2_0.ISoundTriggerHw.Properties();
-        properties.implementor = "implementor";
-        properties.description = "description";
-        properties.version = 123;
-        properties.uuid = new Uuid();
-        properties.uuid.timeLow = 1;
-        properties.uuid.timeMid = 2;
-        properties.uuid.versionAndTimeHigh = 3;
-        properties.uuid.variantAndClockSeqHigh = 4;
-        properties.uuid.node = new byte[]{5, 6, 7, 8, 9, 10};
-
-        properties.maxSoundModels = 456;
-        properties.maxKeyPhrases = 567;
-        properties.maxUsers = 678;
-        properties.recognitionModes =
-                android.hardware.soundtrigger.V2_0.RecognitionMode.VOICE_TRIGGER
-                | android.hardware.soundtrigger.V2_0.RecognitionMode.USER_IDENTIFICATION
-                | android.hardware.soundtrigger.V2_0.RecognitionMode.USER_AUTHENTICATION
-                | android.hardware.soundtrigger.V2_0.RecognitionMode.GENERIC_TRIGGER;
-        properties.captureTransition = true;
-        properties.maxBufferMs = 321;
-        properties.concurrentCapture = supportConcurrentCapture;
-        properties.triggerInEvent = true;
-        properties.powerConsumptionMw = 432;
-        return properties;
-    }
-
-    private static android.hardware.soundtrigger.V2_3.Properties createDefaultProperties_2_3(
-            boolean supportConcurrentCapture) {
-        android.hardware.soundtrigger.V2_3.Properties properties =
-                new android.hardware.soundtrigger.V2_3.Properties();
-        properties.base = createDefaultProperties(supportConcurrentCapture);
-        properties.supportedModelArch = "supportedModelArch";
-        properties.audioCapabilities =
-                android.hardware.soundtrigger.V2_3.AudioCapabilities.ECHO_CANCELLATION
-                        | android.hardware.soundtrigger.V2_3.AudioCapabilities.NOISE_SUPPRESSION;
-        return properties;
-    }
-
-    private void validateDefaultProperties(SoundTriggerModuleProperties properties,
-            boolean supportConcurrentCapture) {
-        assertEquals("implementor", properties.implementor);
-        assertEquals("description", properties.description);
-        assertEquals(123, properties.version);
-        assertEquals("00000001-0002-0003-0004-05060708090a", properties.uuid);
-        assertEquals(456, properties.maxSoundModels);
-        assertEquals(567, properties.maxKeyPhrases);
-        assertEquals(678, properties.maxUsers);
-        assertEquals(RecognitionMode.GENERIC_TRIGGER
-                | RecognitionMode.USER_AUTHENTICATION
-                | RecognitionMode.USER_IDENTIFICATION
-                | RecognitionMode.VOICE_TRIGGER, properties.recognitionModes);
-        assertTrue(properties.captureTransition);
-        assertEquals(321, properties.maxBufferMs);
-        assertEquals(supportConcurrentCapture, properties.concurrentCapture);
-        assertTrue(properties.triggerInEvent);
-        assertEquals(432, properties.powerConsumptionMw);
-
-        if (mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw) {
-            assertEquals("supportedModelArch", properties.supportedModelArch);
-            assertEquals(AudioCapabilities.ECHO_CANCELLATION | AudioCapabilities.NOISE_SUPPRESSION,
-                    properties.audioCapabilities);
-        } else {
-            assertEquals("", properties.supportedModelArch);
-            assertEquals(0, properties.audioCapabilities);
-        }
-    }
-
-    private void verifyNotGetProperties() throws RemoteException {
-        if (mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw) {
-            verify((android.hardware.soundtrigger.V2_3.ISoundTriggerHw) mHalDriver,
-                    never()).getProperties(any());
-        }
-    }
-
-    private static android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionEvent createRecognitionEvent_2_0(
-            int hwHandle,
-            int status) {
-        android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionEvent halEvent =
-                new android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionEvent();
-        halEvent.status = status;
-        halEvent.type = SoundModelType.GENERIC;
-        halEvent.model = hwHandle;
-        halEvent.captureAvailable = true;
-        // This field is ignored.
-        halEvent.captureSession = 123;
-        halEvent.captureDelayMs = 234;
-        halEvent.capturePreambleMs = 345;
-        halEvent.triggerInData = true;
-        halEvent.audioConfig = new AudioConfig();
-        halEvent.audioConfig.sampleRateHz = 456;
-        halEvent.audioConfig.channelMask = AudioChannelMask.IN_LEFT;
-        halEvent.audioConfig.format = AudioFormat.MP3;
-        // hwEvent.audioConfig.offloadInfo is irrelevant.
-        halEvent.data.add((byte) 31);
-        halEvent.data.add((byte) 32);
-        halEvent.data.add((byte) 33);
-        return halEvent;
-    }
-
-    private static android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.RecognitionEvent createRecognitionEvent_2_1(
-            int hwHandle,
-            int status) {
-        android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.RecognitionEvent halEvent =
-                new android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.RecognitionEvent();
-        halEvent.header = createRecognitionEvent_2_0(hwHandle, status);
-        halEvent.header.data.clear();
-        halEvent.data = HidlMemoryUtil.byteArrayToHidlMemory(new byte[]{31, 32, 33});
-        return halEvent;
-    }
-
-    private static void validateRecognitionEvent(RecognitionEvent event, int status) {
-        assertEquals(status, event.status);
-        assertEquals(SoundModelType.GENERIC, event.type);
-        assertTrue(event.captureAvailable);
-        assertEquals(101, event.captureSession);
-        assertEquals(234, event.captureDelayMs);
-        assertEquals(345, event.capturePreambleMs);
-        assertTrue(event.triggerInData);
-        assertEquals(456, event.audioConfig.sampleRateHz);
-        assertEquals(AudioChannelMask.IN_LEFT, event.audioConfig.channelMask);
-        assertEquals(AudioFormat.MP3, event.audioConfig.format);
-    }
-
-    private static android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.PhraseRecognitionEvent createPhraseRecognitionEvent_2_0(
-            int hwHandle, int status) {
-        android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.PhraseRecognitionEvent halEvent =
-                new android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.PhraseRecognitionEvent();
-        halEvent.common = createRecognitionEvent_2_0(hwHandle, status);
-
-        android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra halExtra =
-                new android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra();
-        halExtra.id = 123;
-        halExtra.confidenceLevel = 52;
-        halExtra.recognitionModes = android.hardware.soundtrigger.V2_0.RecognitionMode.VOICE_TRIGGER
-                | android.hardware.soundtrigger.V2_0.RecognitionMode.GENERIC_TRIGGER;
-        android.hardware.soundtrigger.V2_0.ConfidenceLevel halLevel =
-                new android.hardware.soundtrigger.V2_0.ConfidenceLevel();
-        halLevel.userId = 31;
-        halLevel.levelPercent = 43;
-        halExtra.levels.add(halLevel);
-        halEvent.phraseExtras.add(halExtra);
-        return halEvent;
-    }
-
-    private static android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.PhraseRecognitionEvent createPhraseRecognitionEvent_2_1(
-            int hwHandle, int status) {
-        android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.PhraseRecognitionEvent halEvent =
-                new android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.PhraseRecognitionEvent();
-        halEvent.common = createRecognitionEvent_2_1(hwHandle, status);
-
-        android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra halExtra =
-                new android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra();
-        halExtra.id = 123;
-        halExtra.confidenceLevel = 52;
-        halExtra.recognitionModes = android.hardware.soundtrigger.V2_0.RecognitionMode.VOICE_TRIGGER
-                | android.hardware.soundtrigger.V2_0.RecognitionMode.GENERIC_TRIGGER;
-        android.hardware.soundtrigger.V2_0.ConfidenceLevel halLevel =
-                new android.hardware.soundtrigger.V2_0.ConfidenceLevel();
-        halLevel.userId = 31;
-        halLevel.levelPercent = 43;
-        halExtra.levels.add(halLevel);
-        halEvent.phraseExtras.add(halExtra);
-        return halEvent;
-    }
-
-    private static void validatePhraseRecognitionEvent(PhraseRecognitionEvent event, int status) {
-        validateRecognitionEvent(event.common, status);
-
-        assertEquals(1, event.phraseExtras.length);
-        assertEquals(123, event.phraseExtras[0].id);
-        assertEquals(52, event.phraseExtras[0].confidenceLevel);
-        assertEquals(RecognitionMode.VOICE_TRIGGER | RecognitionMode.GENERIC_TRIGGER,
-                event.phraseExtras[0].recognitionModes);
-        assertEquals(1, event.phraseExtras[0].levels.length);
-        assertEquals(31, event.phraseExtras[0].levels[0].userId);
-        assertEquals(43, event.phraseExtras[0].levels[0].levelPercent);
-    }
-
-    private void initService(boolean supportConcurrentCapture) throws RemoteException {
-        doAnswer(invocation -> {
-            android.hardware.soundtrigger.V2_0.ISoundTriggerHw.Properties properties =
-                    createDefaultProperties(
-                            supportConcurrentCapture);
-            ((android.hardware.soundtrigger.V2_0.ISoundTriggerHw.getPropertiesCallback) invocation.getArgument(
-                    0)).onValues(0,
-                    properties);
-            return null;
-        }).when(mHalDriver).getProperties(any());
-
-        if (mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw) {
-            android.hardware.soundtrigger.V2_3.ISoundTriggerHw driver =
-                    (android.hardware.soundtrigger.V2_3.ISoundTriggerHw) mHalDriver;
-            doAnswer(invocation -> {
-                android.hardware.soundtrigger.V2_3.Properties properties =
-                        createDefaultProperties_2_3(
-                                supportConcurrentCapture);
-                ((android.hardware.soundtrigger.V2_3.ISoundTriggerHw.getProperties_2_3Callback)
-                        invocation.getArgument(
-                        0)).onValues(0,
-                        properties);
-                return null;
-            }).when(driver).getProperties_2_3(any());
-        }
-
-        mService = new SoundTriggerMiddlewareImpl(() -> {
-            return mHalDriver;
-        }, mAudioSessionProvider);
-    }
-
-    private Pair<Integer, SoundTriggerHwCallback> loadGenericModel_2_0(ISoundTriggerModule module,
-            int hwHandle) throws RemoteException {
-        SoundModel model = createGenericSoundModel();
-        ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHw.SoundModel> modelCaptor =
-                ArgumentCaptor.forClass(
-                        android.hardware.soundtrigger.V2_0.ISoundTriggerHw.SoundModel.class);
-        ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback> callbackCaptor =
-                ArgumentCaptor.forClass(
-                        android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.class);
-        ArgumentCaptor<Integer> cookieCaptor = ArgumentCaptor.forClass(Integer.class);
-
-        doAnswer(invocation -> {
-            android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback callback =
-                    invocation.getArgument(1);
-            int callbackCookie = invocation.getArgument(2);
-            android.hardware.soundtrigger.V2_0.ISoundTriggerHw.loadSoundModelCallback
-                    resultCallback = invocation.getArgument(3);
-
-            // This is the return of this method.
-            resultCallback.onValues(0, hwHandle);
-
-            // This is the async mCallback that comes after.
-            android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.ModelEvent modelEvent =
-                    new android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.ModelEvent();
-            modelEvent.status =
-                    android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.SoundModelStatus.UPDATED;
-            modelEvent.model = hwHandle;
-            callback.soundModelCallback(modelEvent, callbackCookie);
-            return null;
-        }).when(mHalDriver).loadSoundModel(modelCaptor.capture(), callbackCaptor.capture(),
-                cookieCaptor.capture(), any());
-
-        when(mAudioSessionProvider.acquireSession()).thenReturn(
-                new SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession(101, 102, 103));
-
-        int handle = module.loadModel(model);
-        verify(mHalDriver).loadSoundModel(any(), any(), anyInt(), any());
-        verify(mAudioSessionProvider).acquireSession();
-
-        android.hardware.soundtrigger.V2_0.ISoundTriggerHw.SoundModel hidlModel =
-                modelCaptor.getValue();
-        assertEquals(android.hardware.soundtrigger.V2_0.SoundModelType.GENERIC,
-                hidlModel.type);
-        assertEquals(model.uuid, ConversionUtil.hidl2aidlUuid(hidlModel.uuid));
-        assertEquals(model.vendorUuid, ConversionUtil.hidl2aidlUuid(hidlModel.vendorUuid));
-        assertArrayEquals(new Byte[]{91, 92, 93, 94, 95}, hidlModel.data.toArray());
-
-        return new Pair<>(handle,
-                new SoundTriggerHwCallback(callbackCaptor.getValue(), cookieCaptor.getValue()));
-    }
-
-    private Pair<Integer, SoundTriggerHwCallback> loadGenericModel_2_1(ISoundTriggerModule module,
-            int hwHandle) throws RemoteException {
-        android.hardware.soundtrigger.V2_1.ISoundTriggerHw driver =
-                (android.hardware.soundtrigger.V2_1.ISoundTriggerHw) mHalDriver;
-        SoundModel model = createGenericSoundModel();
-        ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel> modelCaptor =
-                ArgumentCaptor.forClass(
-                        android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel.class);
-        ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback> callbackCaptor =
-                ArgumentCaptor.forClass(
-                        android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.class);
-        ArgumentCaptor<Integer> cookieCaptor = ArgumentCaptor.forClass(Integer.class);
-
-        doAnswer(invocation -> {
-            android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback callback =
-                    invocation.getArgument(1);
-            int callbackCookie = invocation.getArgument(2);
-            android.hardware.soundtrigger.V2_1.ISoundTriggerHw.loadSoundModel_2_1Callback
-                    resultCallback = invocation.getArgument(3);
-
-            // This is the return of this method.
-            resultCallback.onValues(0, hwHandle);
-
-            // This is the async mCallback that comes after.
-            android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.ModelEvent modelEvent =
-                    new android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.ModelEvent();
-            modelEvent.header.status =
-                    android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.SoundModelStatus.UPDATED;
-            modelEvent.header.model = hwHandle;
-            callback.soundModelCallback_2_1(modelEvent, callbackCookie);
-            return null;
-        }).when(driver).loadSoundModel_2_1(modelCaptor.capture(), callbackCaptor.capture(),
-                cookieCaptor.capture(), any());
-
-        when(mAudioSessionProvider.acquireSession()).thenReturn(
-                new SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession(101, 102, 103));
-
-        int handle = module.loadModel(model);
-        verify(driver).loadSoundModel_2_1(any(), any(), anyInt(), any());
-        verify(mAudioSessionProvider).acquireSession();
-
-        android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel hidlModel =
-                modelCaptor.getValue();
-        assertEquals(android.hardware.soundtrigger.V2_0.SoundModelType.GENERIC,
-                hidlModel.header.type);
-        assertEquals(model.uuid, ConversionUtil.hidl2aidlUuid(hidlModel.header.uuid));
-        assertEquals(model.vendorUuid, ConversionUtil.hidl2aidlUuid(hidlModel.header.vendorUuid));
-        assertArrayEquals(new byte[]{91, 92, 93, 94, 95},
-                HidlMemoryUtil.hidlMemoryToByteArray(hidlModel.data));
-
-        return new Pair<>(handle,
-                new SoundTriggerHwCallback(callbackCaptor.getValue(), cookieCaptor.getValue()));
-    }
-
     private Pair<Integer, SoundTriggerHwCallback> loadGenericModel(ISoundTriggerModule module,
             int hwHandle) throws RemoteException {
-        if (mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw) {
-            return loadGenericModel_2_1(module, hwHandle);
-        } else {
-            return loadGenericModel_2_0(module, hwHandle);
-        }
+        SoundModel model = TestUtil.createGenericSoundModel();
+        ArgumentCaptor<SoundModel> modelCaptor = ArgumentCaptor.forClass(SoundModel.class);
+        ArgumentCaptor<ISoundTriggerHal.ModelCallback> callbackCaptor = ArgumentCaptor.forClass(
+                ISoundTriggerHal.ModelCallback.class);
+
+        when(mHalDriver.loadSoundModel(any(), any())).thenReturn(hwHandle);
+        when(mAudioSessionProvider.acquireSession()).thenReturn(
+                new SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession(101, 102, 103));
+
+        int handle = module.loadModel(model);
+        verify(mHalDriver).loadSoundModel(modelCaptor.capture(), callbackCaptor.capture());
+        verify(mAudioSessionProvider).acquireSession();
+        assertEquals(model, modelCaptor.getValue());
+        return new Pair<>(handle, new SoundTriggerHwCallback(callbackCaptor.getValue()));
     }
 
-    private Pair<Integer, SoundTriggerHwCallback> loadPhraseModel_2_0(ISoundTriggerModule module,
+    private Pair<Integer, SoundTriggerHwCallback> loadPhraseModel(ISoundTriggerModule module,
             int hwHandle) throws RemoteException {
-        PhraseSoundModel model = createPhraseSoundModel();
-        ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHw.PhraseSoundModel>
-                modelCaptor = ArgumentCaptor.forClass(
-                android.hardware.soundtrigger.V2_0.ISoundTriggerHw.PhraseSoundModel.class);
-        ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback> callbackCaptor =
-                ArgumentCaptor.forClass(
-                        android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.class);
-        ArgumentCaptor<Integer> cookieCaptor = ArgumentCaptor.forClass(Integer.class);
+        PhraseSoundModel model = TestUtil.createPhraseSoundModel();
+        ArgumentCaptor<PhraseSoundModel> modelCaptor = ArgumentCaptor.forClass(
+                PhraseSoundModel.class);
+        ArgumentCaptor<ISoundTriggerHal.ModelCallback> callbackCaptor = ArgumentCaptor.forClass(
+                ISoundTriggerHal.ModelCallback.class);
 
-        doAnswer(invocation -> {
-            android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback callback =
-                    invocation.getArgument(
-                            1);
-            int callbackCookie = invocation.getArgument(2);
-            android.hardware.soundtrigger.V2_0.ISoundTriggerHw.loadPhraseSoundModelCallback
-                    resultCallback =
-                    invocation.getArgument(
-                            3);
-
-            // This is the return of this method.
-            resultCallback.onValues(0, hwHandle);
-
-            // This is the async mCallback that comes after.
-            android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.ModelEvent modelEvent =
-                    new android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.ModelEvent();
-            modelEvent.status =
-                    android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.SoundModelStatus.UPDATED;
-            modelEvent.model = hwHandle;
-            callback.soundModelCallback(modelEvent, callbackCookie);
-            return null;
-        }).when(mHalDriver).loadPhraseSoundModel(modelCaptor.capture(), callbackCaptor.capture(),
-                cookieCaptor.capture(), any());
-
+        when(mHalDriver.loadPhraseSoundModel(any(), any())).thenReturn(hwHandle);
         when(mAudioSessionProvider.acquireSession()).thenReturn(
                 new SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession(101, 102, 103));
 
         int handle = module.loadPhraseModel(model);
-        verify(mHalDriver).loadPhraseSoundModel(any(), any(), anyInt(), any());
+        verify(mHalDriver).loadPhraseSoundModel(modelCaptor.capture(), callbackCaptor.capture());
         verify(mAudioSessionProvider).acquireSession();
-
-        android.hardware.soundtrigger.V2_0.ISoundTriggerHw.PhraseSoundModel hidlModel =
-                modelCaptor.getValue();
-
-        // Validate common part.
-        assertEquals(android.hardware.soundtrigger.V2_0.SoundModelType.KEYPHRASE,
-                hidlModel.common.type);
-        assertEquals(model.common.uuid, ConversionUtil.hidl2aidlUuid(hidlModel.common.uuid));
-        assertEquals(model.common.vendorUuid,
-                ConversionUtil.hidl2aidlUuid(hidlModel.common.vendorUuid));
-        assertArrayEquals(new Byte[]{91, 92, 93, 94, 95}, hidlModel.common.data.toArray());
-
-        // Validate phrase part.
-        assertEquals(1, hidlModel.phrases.size());
-        android.hardware.soundtrigger.V2_0.ISoundTriggerHw.Phrase hidlPhrase =
-                hidlModel.phrases.get(0);
-        assertEquals(123, hidlPhrase.id);
-        assertArrayEquals(new Integer[]{5, 6, 7}, hidlPhrase.users.toArray());
-        assertEquals("locale", hidlPhrase.locale);
-        assertEquals("text", hidlPhrase.text);
-        assertEquals(android.hardware.soundtrigger.V2_0.RecognitionMode.USER_AUTHENTICATION
-                        | android.hardware.soundtrigger.V2_0.RecognitionMode.USER_IDENTIFICATION,
-                hidlPhrase.recognitionModes);
-
-        return new Pair<>(handle,
-                new SoundTriggerHwCallback(callbackCaptor.getValue(), cookieCaptor.getValue()));
-    }
-
-    private Pair<Integer, SoundTriggerHwCallback> loadPhraseModel_2_1(ISoundTriggerModule module,
-            int hwHandle) throws RemoteException {
-        android.hardware.soundtrigger.V2_1.ISoundTriggerHw driver =
-                (android.hardware.soundtrigger.V2_1.ISoundTriggerHw) mHalDriver;
-
-        PhraseSoundModel model = createPhraseSoundModel();
-        ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHw.PhraseSoundModel>
-                modelCaptor = ArgumentCaptor.forClass(
-                android.hardware.soundtrigger.V2_1.ISoundTriggerHw.PhraseSoundModel.class);
-        ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback> callbackCaptor =
-                ArgumentCaptor.forClass(
-                        android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.class);
-        ArgumentCaptor<Integer> cookieCaptor = ArgumentCaptor.forClass(Integer.class);
-
-        doAnswer(invocation -> {
-            android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback callback =
-                    invocation.getArgument(
-                            1);
-            int callbackCookie = invocation.getArgument(2);
-            android.hardware.soundtrigger.V2_1.ISoundTriggerHw.loadPhraseSoundModel_2_1Callback
-                    resultCallback =
-                    invocation.getArgument(
-                            3);
-
-            // This is the return of this method.
-            resultCallback.onValues(0, hwHandle);
-
-            // This is the async mCallback that comes after.
-            android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.ModelEvent modelEvent =
-                    new android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.ModelEvent();
-            modelEvent.header.status =
-                    android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.SoundModelStatus.UPDATED;
-            modelEvent.header.model = hwHandle;
-            callback.soundModelCallback_2_1(modelEvent, callbackCookie);
-            return null;
-        }).when(driver).loadPhraseSoundModel_2_1(modelCaptor.capture(), callbackCaptor.capture(),
-                cookieCaptor.capture(), any());
-
-        when(mAudioSessionProvider.acquireSession()).thenReturn(
-                new SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession(101, 102, 103));
-
-        int handle = module.loadPhraseModel(model);
-        verify(driver).loadPhraseSoundModel_2_1(any(), any(), anyInt(), any());
-        verify(mAudioSessionProvider).acquireSession();
-
-        android.hardware.soundtrigger.V2_1.ISoundTriggerHw.PhraseSoundModel hidlModel =
-                modelCaptor.getValue();
-
-        // Validate common part.
-        assertEquals(android.hardware.soundtrigger.V2_0.SoundModelType.KEYPHRASE,
-                hidlModel.common.header.type);
-        assertEquals(model.common.uuid, ConversionUtil.hidl2aidlUuid(hidlModel.common.header.uuid));
-        assertEquals(model.common.vendorUuid,
-                ConversionUtil.hidl2aidlUuid(hidlModel.common.header.vendorUuid));
-        assertArrayEquals(new byte[]{91, 92, 93, 94, 95},
-                HidlMemoryUtil.hidlMemoryToByteArray(hidlModel.common.data));
-
-        // Validate phrase part.
-        assertEquals(1, hidlModel.phrases.size());
-        android.hardware.soundtrigger.V2_1.ISoundTriggerHw.Phrase hidlPhrase =
-                hidlModel.phrases.get(0);
-        assertEquals(123, hidlPhrase.id);
-        assertArrayEquals(new Integer[]{5, 6, 7}, hidlPhrase.users.toArray());
-        assertEquals("locale", hidlPhrase.locale);
-        assertEquals("text", hidlPhrase.text);
-        assertEquals(android.hardware.soundtrigger.V2_0.RecognitionMode.USER_AUTHENTICATION
-                        | android.hardware.soundtrigger.V2_0.RecognitionMode.USER_IDENTIFICATION,
-                hidlPhrase.recognitionModes);
-
-        return new Pair<>(handle,
-                new SoundTriggerHwCallback(callbackCaptor.getValue(), cookieCaptor.getValue()));
-    }
-
-    private Pair<Integer, SoundTriggerHwCallback> loadPhraseModel(
-            ISoundTriggerModule module, int hwHandle) throws RemoteException {
-        if (mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw) {
-            return loadPhraseModel_2_1(module, hwHandle);
-        } else {
-            return loadPhraseModel_2_0(module, hwHandle);
-        }
+        assertEquals(model, modelCaptor.getValue());
+        return new Pair<>(handle, new SoundTriggerHwCallback(callbackCaptor.getValue()));
     }
 
     private void unloadModel(ISoundTriggerModule module, int handle, int hwHandle)
@@ -631,204 +111,35 @@
         verify(mAudioSessionProvider).releaseSession(101);
     }
 
-    private void startRecognition_2_0(ISoundTriggerModule module, int handle,
-            int hwHandle) throws RemoteException {
-        ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig>
-                configCaptor = ArgumentCaptor.forClass(
-                android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig.class);
+    private void startRecognition(ISoundTriggerModule module, int handle, int hwHandle)
+            throws RemoteException {
+        ArgumentCaptor<RecognitionConfig> configCaptor = ArgumentCaptor.forClass(
+                RecognitionConfig.class);
 
-        when(mHalDriver.startRecognition(eq(hwHandle), configCaptor.capture(), any(), anyInt()))
-                .thenReturn(0);
-
-        RecognitionConfig config = createRecognitionConfig();
+        RecognitionConfig config = TestUtil.createRecognitionConfig();
 
         module.startRecognition(handle, config);
-        verify(mHalDriver).startRecognition(eq(hwHandle), any(), any(), anyInt());
-
-        android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig halConfig =
-                configCaptor.getValue();
-        assertTrue(halConfig.captureRequested);
-        assertEquals(102, halConfig.captureHandle);
-        assertEquals(103, halConfig.captureDevice);
-        assertEquals(1, halConfig.phrases.size());
-        android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra halPhraseExtra =
-                halConfig.phrases.get(0);
-        assertEquals(123, halPhraseExtra.id);
-        assertEquals(4, halPhraseExtra.confidenceLevel);
-        assertEquals(5, halPhraseExtra.recognitionModes);
-        assertEquals(1, halPhraseExtra.levels.size());
-        android.hardware.soundtrigger.V2_0.ConfidenceLevel halLevel = halPhraseExtra.levels.get(0);
-        assertEquals(234, halLevel.userId);
-        assertEquals(34, halLevel.levelPercent);
-        assertArrayEquals(new Byte[]{5, 4, 3, 2, 1}, halConfig.data.toArray());
-    }
-
-    private void startRecognition_2_1(ISoundTriggerModule module, int handle,
-            int hwHandle) throws RemoteException {
-        android.hardware.soundtrigger.V2_1.ISoundTriggerHw driver =
-                (android.hardware.soundtrigger.V2_1.ISoundTriggerHw) mHalDriver;
-
-        ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig>
-                configCaptor = ArgumentCaptor.forClass(
-                android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig.class);
-
-        when(driver.startRecognition_2_1(eq(hwHandle), configCaptor.capture(), any(), anyInt()))
-                .thenReturn(0);
-
-        RecognitionConfig config = createRecognitionConfig();
-
-        module.startRecognition(handle, config);
-        verify(driver).startRecognition_2_1(eq(hwHandle), any(), any(), anyInt());
-
-        android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig halConfig =
-                configCaptor.getValue();
-        assertTrue(halConfig.header.captureRequested);
-        assertEquals(102, halConfig.header.captureHandle);
-        assertEquals(103, halConfig.header.captureDevice);
-        assertEquals(1, halConfig.header.phrases.size());
-        android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra halPhraseExtra =
-                halConfig.header.phrases.get(0);
-        assertEquals(123, halPhraseExtra.id);
-        assertEquals(4, halPhraseExtra.confidenceLevel);
-        assertEquals(5, halPhraseExtra.recognitionModes);
-        assertEquals(1, halPhraseExtra.levels.size());
-        android.hardware.soundtrigger.V2_0.ConfidenceLevel halLevel = halPhraseExtra.levels.get(0);
-        assertEquals(234, halLevel.userId);
-        assertEquals(34, halLevel.levelPercent);
-        assertArrayEquals(new byte[]{5, 4, 3, 2, 1},
-                HidlMemoryUtil.hidlMemoryToByteArray(halConfig.data));
-    }
-
-    private void startRecognition_2_3(ISoundTriggerModule module, int handle,
-            int hwHandle) throws RemoteException {
-        android.hardware.soundtrigger.V2_3.ISoundTriggerHw driver =
-                (android.hardware.soundtrigger.V2_3.ISoundTriggerHw) mHalDriver;
-
-        ArgumentCaptor<android.hardware.soundtrigger.V2_3.RecognitionConfig>
-                configCaptor = ArgumentCaptor.forClass(
-                android.hardware.soundtrigger.V2_3.RecognitionConfig.class);
-
-        when(driver.startRecognition_2_3(eq(hwHandle), configCaptor.capture())).thenReturn(0);
-
-        RecognitionConfig config = createRecognitionConfig();
-
-        module.startRecognition(handle, config);
-        verify(driver).startRecognition_2_3(eq(hwHandle), any());
-
-        android.hardware.soundtrigger.V2_3.RecognitionConfig halConfigExtended =
-                configCaptor.getValue();
-        android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig halConfig_2_1 =
-                halConfigExtended.base;
-
-        assertTrue(halConfig_2_1.header.captureRequested);
-        assertEquals(102, halConfig_2_1.header.captureHandle);
-        assertEquals(103, halConfig_2_1.header.captureDevice);
-        assertEquals(1, halConfig_2_1.header.phrases.size());
-        android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra halPhraseExtra =
-                halConfig_2_1.header.phrases.get(0);
-        assertEquals(123, halPhraseExtra.id);
-        assertEquals(4, halPhraseExtra.confidenceLevel);
-        assertEquals(5, halPhraseExtra.recognitionModes);
-        assertEquals(1, halPhraseExtra.levels.size());
-        android.hardware.soundtrigger.V2_0.ConfidenceLevel halLevel = halPhraseExtra.levels.get(0);
-        assertEquals(234, halLevel.userId);
-        assertEquals(34, halLevel.levelPercent);
-        assertArrayEquals(new byte[]{5, 4, 3, 2, 1},
-                HidlMemoryUtil.hidlMemoryToByteArray(halConfig_2_1.data));
-        assertEquals(AudioCapabilities.ECHO_CANCELLATION
-                | AudioCapabilities.NOISE_SUPPRESSION, halConfigExtended.audioCapabilities);
-    }
-
-    private void startRecognition(ISoundTriggerModule module, int handle,
-            int hwHandle) throws RemoteException {
-        if (mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw) {
-            startRecognition_2_3(module, handle, hwHandle);
-        } else if (mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw) {
-            startRecognition_2_1(module, handle, hwHandle);
-        } else {
-            startRecognition_2_0(module, handle, hwHandle);
-        }
-    }
-
-    private RecognitionConfig createRecognitionConfig() {
-        RecognitionConfig config = new RecognitionConfig();
-        config.captureRequested = true;
-        config.phraseRecognitionExtras = new PhraseRecognitionExtra[]{new PhraseRecognitionExtra()};
-        config.phraseRecognitionExtras[0].id = 123;
-        config.phraseRecognitionExtras[0].confidenceLevel = 4;
-        config.phraseRecognitionExtras[0].recognitionModes = 5;
-        config.phraseRecognitionExtras[0].levels = new ConfidenceLevel[]{new ConfidenceLevel()};
-        config.phraseRecognitionExtras[0].levels[0].userId = 234;
-        config.phraseRecognitionExtras[0].levels[0].levelPercent = 34;
-        config.data = new byte[]{5, 4, 3, 2, 1};
-        config.audioCapabilities = AudioCapabilities.ECHO_CANCELLATION
-                | AudioCapabilities.NOISE_SUPPRESSION;
-        return config;
+        verify(mHalDriver).startRecognition(eq(hwHandle), eq(103), eq(102), configCaptor.capture());
+        assertEquals(config, configCaptor.getValue());
     }
 
     private void stopRecognition(ISoundTriggerModule module, int handle, int hwHandle)
             throws RemoteException {
-        when(mHalDriver.stopRecognition(hwHandle)).thenReturn(0);
         module.stopRecognition(handle);
         verify(mHalDriver).stopRecognition(hwHandle);
     }
 
-    private void verifyNotStartRecognition() throws RemoteException {
-        verify(mHalDriver, never()).startRecognition(anyInt(), any(), any(), anyInt());
-        if (mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw) {
-            verify((android.hardware.soundtrigger.V2_1.ISoundTriggerHw) mHalDriver,
-                    never()).startRecognition_2_1(anyInt(), any(), any(), anyInt());
-        } else if (mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw) {
-            verify((android.hardware.soundtrigger.V2_3.ISoundTriggerHw) mHalDriver,
-                    never()).startRecognition_2_3(anyInt(), any());
-        }
-    }
-
-
     @Before
     public void setUp() throws Exception {
         clearInvocations(mHalDriver);
         clearInvocations(mAudioSessionProvider);
+        when(mHalDriver.getProperties()).thenReturn(TestUtil.createDefaultProperties(false));
+        mService = new SoundTriggerMiddlewareImpl(() -> mHalDriver, mAudioSessionProvider);
+    }
 
-        // This binder is associated with the mock, so it can be cast to either version of the
-        // HAL interface.
-        final IHwBinder binder = new IHwBinder() {
-            @Override
-            public void transact(int code, HwParcel request, HwParcel reply, int flags)
-                    throws RemoteException {
-                // This is a little hacky, but a very easy way to gracefully reject a request for
-                // an unsupported interface (after queryLocalInterface() returns null, the client
-                // will attempt a remote transaction to obtain the interface. RemoteException will
-                // cause it to give up).
-                throw new RemoteException();
-            }
-
-            @Override
-            public IHwInterface queryLocalInterface(String descriptor) {
-                if (descriptor.equals("android.hardware.soundtrigger@2.0::ISoundTriggerHw")
-                        || descriptor.equals("android.hardware.soundtrigger@2.1::ISoundTriggerHw")
-                        && mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw
-                        || descriptor.equals("android.hardware.soundtrigger@2.2::ISoundTriggerHw")
-                        && mHalDriver instanceof android.hardware.soundtrigger.V2_2.ISoundTriggerHw
-                        || descriptor.equals("android.hardware.soundtrigger@2.3::ISoundTriggerHw")
-                        && mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw) {
-                    return mHalDriver;
-                }
-                return null;
-            }
-
-            @Override
-            public boolean linkToDeath(DeathRecipient recipient, long cookie) {
-                return true;
-            }
-
-            @Override
-            public boolean unlinkToDeath(DeathRecipient recipient) {
-                return true;
-            }
-        };
-
-        when(mHalDriver.asBinder()).thenReturn(binder);
+    @After
+    public void tearDown() {
+        verify(mHalDriver, never()).reboot();
     }
 
     @Test
@@ -836,58 +147,28 @@
     }
 
     @Test
-    public void testListModules() throws Exception {
-        initService(true);
+    public void testListModules() {
         // Note: input and output properties are NOT the same type, even though they are in any way
         // equivalent. One is a type that's exposed by the HAL and one is a type that's exposed by
         // the service. The service actually performs a (trivial) conversion between the two.
         SoundTriggerModuleDescriptor[] allDescriptors = mService.listModules();
         assertEquals(1, allDescriptors.length);
 
-        SoundTriggerModuleProperties properties = allDescriptors[0].properties;
-
-        validateDefaultProperties(properties, true);
-        verifyNotGetProperties();
+        Properties properties = allDescriptors[0].properties;
+        assertEquals(TestUtil.createDefaultProperties(false), properties);
     }
 
     @Test
     public void testAttachDetach() throws Exception {
         // Normal attachment / detachment.
-        initService(true);
         ISoundTriggerCallback callback = createCallbackMock();
         ISoundTriggerModule module = mService.attach(0, callback);
-        verify(callback).onRecognitionAvailabilityChange(true);
-        assertNotNull(module);
-        module.detach();
-    }
-
-    @Test
-    public void testAttachDetachNotAvailable() throws Exception {
-        // Attachment / detachment during external capture, with a module not supporting concurrent
-        // capture.
-        initService(false);
-        ISoundTriggerCallback callback = createCallbackMock();
-        ISoundTriggerModule module = mService.attach(0, callback);
-        verify(callback).onRecognitionAvailabilityChange(false);
-        assertNotNull(module);
-        module.detach();
-    }
-
-    @Test
-    public void testAttachDetachAvailable() throws Exception {
-        // Attachment / detachment during external capture, with a module supporting concurrent
-        // capture.
-        initService(true);
-        ISoundTriggerCallback callback = createCallbackMock();
-        ISoundTriggerModule module = mService.attach(0, callback);
-        verify(callback).onRecognitionAvailabilityChange(true);
         assertNotNull(module);
         module.detach();
     }
 
     @Test
     public void testLoadUnloadModel() throws Exception {
-        initService(true);
         ISoundTriggerCallback callback = createCallbackMock();
         ISoundTriggerModule module = mService.attach(0, callback);
 
@@ -898,8 +179,26 @@
     }
 
     @Test
+    public void testLoadPreemptModel() throws Exception {
+        ISoundTriggerCallback callback = createCallbackMock();
+        ISoundTriggerModule module = mService.attach(0, callback);
+
+        final int hwHandle = 7;
+        Pair<Integer, SoundTriggerHwCallback> loadResult = loadGenericModel(module, hwHandle);
+
+        int handle = loadResult.first;
+        SoundTriggerHwCallback hwCallback = loadResult.second;
+
+        // Signal preemption.
+        hwCallback.sendUnloadEvent(hwHandle);
+
+        verify(callback).onModelUnloaded(handle);
+
+        module.detach();
+    }
+
+    @Test
     public void testLoadUnloadPhraseModel() throws Exception {
-        initService(true);
         ISoundTriggerCallback callback = createCallbackMock();
         ISoundTriggerModule module = mService.attach(0, callback);
 
@@ -911,7 +210,6 @@
 
     @Test
     public void testStartStopRecognition() throws Exception {
-        initService(true);
         ISoundTriggerCallback callback = createCallbackMock();
         ISoundTriggerModule module = mService.attach(0, callback);
 
@@ -931,8 +229,31 @@
     }
 
     @Test
+    public void testStartRecognitionBusy() throws Exception {
+        ISoundTriggerCallback callback = createCallbackMock();
+        ISoundTriggerModule module = mService.attach(0, callback);
+
+        // Load the model.
+        final int hwHandle = 7;
+        int handle = loadGenericModel(module, hwHandle).first;
+
+        // Start the model.
+        doThrow(new RecoverableException(Status.RESOURCE_CONTENTION)).when(
+                mHalDriver).startRecognition(eq(7), eq(103), eq(102), any());
+
+        try {
+            RecognitionConfig config = TestUtil.createRecognitionConfig();
+            module.startRecognition(handle, config);
+            fail("Expected an exception");
+        } catch (RecoverableException e) {
+            assertEquals(Status.RESOURCE_CONTENTION, e.errorCode);
+        }
+
+        verify(mHalDriver).startRecognition(eq(7), eq(103), eq(102), any());
+    }
+
+    @Test
     public void testStartStopPhraseRecognition() throws Exception {
-        initService(true);
         ISoundTriggerCallback callback = createCallbackMock();
         ISoundTriggerModule module = mService.attach(0, callback);
 
@@ -953,7 +274,6 @@
 
     @Test
     public void testRecognition() throws Exception {
-        initService(true);
         ISoundTriggerCallback callback = createCallbackMock();
         ISoundTriggerModule module = mService.attach(0, callback);
 
@@ -967,15 +287,15 @@
         startRecognition(module, handle, hwHandle);
 
         // Signal a capture from the driver.
-        hwCallback.sendRecognitionEvent(hwHandle,
-                android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionStatus.SUCCESS);
+        RecognitionEvent event = hwCallback.sendRecognitionEvent(hwHandle,
+                RecognitionStatus.SUCCESS);
 
         ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
                 RecognitionEvent.class);
-        verify(callback).onRecognition(eq(handle), eventCaptor.capture());
+        verify(callback).onRecognition(eq(handle), eventCaptor.capture(), eq(101));
 
         // Validate the event.
-        validateRecognitionEvent(eventCaptor.getValue(), RecognitionStatus.SUCCESS);
+        assertEquals(event, eventCaptor.getValue());
 
         // Unload the model.
         unloadModel(module, handle, hwHandle);
@@ -984,7 +304,6 @@
 
     @Test
     public void testPhraseRecognition() throws Exception {
-        initService(true);
         ISoundTriggerCallback callback = createCallbackMock();
         ISoundTriggerModule module = mService.attach(0, callback);
 
@@ -998,15 +317,15 @@
         startRecognition(module, handle, hwHandle);
 
         // Signal a capture from the driver.
-        hwCallback.sendPhraseRecognitionEvent(hwHandle,
-                android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionStatus.SUCCESS);
+        PhraseRecognitionEvent event = hwCallback.sendPhraseRecognitionEvent(hwHandle,
+                RecognitionStatus.SUCCESS);
 
         ArgumentCaptor<PhraseRecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
                 PhraseRecognitionEvent.class);
-        verify(callback).onPhraseRecognition(eq(handle), eventCaptor.capture());
+        verify(callback).onPhraseRecognition(eq(handle), eventCaptor.capture(), eq(101));
 
         // Validate the event.
-        validatePhraseRecognitionEvent(eventCaptor.getValue(), RecognitionStatus.SUCCESS);
+        assertEquals(event, eventCaptor.getValue());
 
         // Unload the model.
         unloadModel(module, handle, hwHandle);
@@ -1015,14 +334,6 @@
 
     @Test
     public void testForceRecognition() throws Exception {
-        if (!(mHalDriver instanceof android.hardware.soundtrigger.V2_2.ISoundTriggerHw)) {
-            return;
-        }
-
-        android.hardware.soundtrigger.V2_2.ISoundTriggerHw driver =
-                (android.hardware.soundtrigger.V2_2.ISoundTriggerHw) mHalDriver;
-
-        initService(true);
         ISoundTriggerCallback callback = createCallbackMock();
         ISoundTriggerModule module = mService.attach(0, callback);
 
@@ -1037,18 +348,49 @@
 
         // Force a trigger.
         module.forceRecognitionEvent(handle);
-        verify(driver).getModelState(hwHandle);
+        verify(mHalDriver).forceRecognitionEvent(hwHandle);
 
         // Signal a capture from the driver.
-        // '3' means 'forced', there's no constant for that in the HAL.
-        hwCallback.sendRecognitionEvent(hwHandle, 3);
+        RecognitionEvent event = hwCallback.sendRecognitionEvent(hwHandle,
+                RecognitionStatus.FORCED);
 
         ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
                 RecognitionEvent.class);
-        verify(callback).onRecognition(eq(handle), eventCaptor.capture());
+        verify(callback).onRecognition(eq(handle), eventCaptor.capture(), eq(101));
 
         // Validate the event.
-        validateRecognitionEvent(eventCaptor.getValue(), RecognitionStatus.FORCED);
+        assertEquals(event, eventCaptor.getValue());
+
+        // Stop the recognition.
+        stopRecognition(module, handle, hwHandle);
+
+        // Unload the model.
+        unloadModel(module, handle, hwHandle);
+        module.detach();
+    }
+
+    @Test
+    public void testForceRecognitionNotSupported() throws Exception {
+        ISoundTriggerCallback callback = createCallbackMock();
+        ISoundTriggerModule module = mService.attach(0, callback);
+
+        // Load the model.
+        final int hwHandle = 17;
+        Pair<Integer, SoundTriggerHwCallback> modelHandles = loadGenericModel(module, hwHandle);
+        int handle = modelHandles.first;
+
+        // Initiate a recognition.
+        startRecognition(module, handle, hwHandle);
+
+        // Force a trigger.
+        doThrow(new RecoverableException(Status.OPERATION_NOT_SUPPORTED)).when(
+                mHalDriver).forceRecognitionEvent(hwHandle);
+        try {
+            module.forceRecognitionEvent(handle);
+            fail("Expected an exception");
+        } catch (RecoverableException e) {
+            assertEquals(Status.OPERATION_NOT_SUPPORTED, e.errorCode);
+        }
 
         // Stop the recognition.
         stopRecognition(module, handle, hwHandle);
@@ -1060,14 +402,6 @@
 
     @Test
     public void testForcePhraseRecognition() throws Exception {
-        if (!(mHalDriver instanceof android.hardware.soundtrigger.V2_2.ISoundTriggerHw)) {
-            return;
-        }
-
-        android.hardware.soundtrigger.V2_2.ISoundTriggerHw driver =
-                (android.hardware.soundtrigger.V2_2.ISoundTriggerHw) mHalDriver;
-
-        initService(true);
         ISoundTriggerCallback callback = createCallbackMock();
         ISoundTriggerModule module = mService.attach(0, callback);
 
@@ -1082,18 +416,49 @@
 
         // Force a trigger.
         module.forceRecognitionEvent(handle);
-        verify(driver).getModelState(hwHandle);
+        verify(mHalDriver).forceRecognitionEvent(hwHandle);
 
         // Signal a capture from the driver.
-        // '3' means 'forced', there's no constant for that in the HAL.
-        hwCallback.sendPhraseRecognitionEvent(hwHandle, 3);
+        PhraseRecognitionEvent event = hwCallback.sendPhraseRecognitionEvent(hwHandle,
+                RecognitionStatus.FORCED);
 
         ArgumentCaptor<PhraseRecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
                 PhraseRecognitionEvent.class);
-        verify(callback).onPhraseRecognition(eq(handle), eventCaptor.capture());
+        verify(callback).onPhraseRecognition(eq(handle), eventCaptor.capture(), eq(101));
 
         // Validate the event.
-        validatePhraseRecognitionEvent(eventCaptor.getValue(), RecognitionStatus.FORCED);
+        assertEquals(event, eventCaptor.getValue());
+
+        // Stop the recognition.
+        stopRecognition(module, handle, hwHandle);
+
+        // Unload the model.
+        unloadModel(module, handle, hwHandle);
+        module.detach();
+    }
+
+    @Test
+    public void testForcePhraseRecognitionNotSupported() throws Exception {
+        ISoundTriggerCallback callback = createCallbackMock();
+        ISoundTriggerModule module = mService.attach(0, callback);
+
+        // Load the model.
+        final int hwHandle = 17;
+        Pair<Integer, SoundTriggerHwCallback> modelHandles = loadPhraseModel(module, hwHandle);
+        int handle = modelHandles.first;
+
+        // Initiate a recognition.
+        startRecognition(module, handle, hwHandle);
+
+        // Force a trigger.
+        doThrow(new RecoverableException(Status.OPERATION_NOT_SUPPORTED)).when(
+                mHalDriver).forceRecognitionEvent(hwHandle);
+        try {
+            module.forceRecognitionEvent(handle);
+            fail("Expected an exception");
+        } catch (RecoverableException e) {
+            assertEquals(Status.OPERATION_NOT_SUPPORTED, e.errorCode);
+        }
 
         // Stop the recognition.
         stopRecognition(module, handle, hwHandle);
@@ -1106,46 +471,28 @@
     @Test
     public void testAbortRecognition() throws Exception {
         // Make sure the HAL doesn't support concurrent capture.
-        initService(false);
-        mService.setCaptureState(false);
-
         ISoundTriggerCallback callback = createCallbackMock();
         ISoundTriggerModule module = mService.attach(0, callback);
-        verify(callback).onRecognitionAvailabilityChange(true);
 
         // Load the model.
         final int hwHandle = 11;
-        int handle = loadGenericModel(module, hwHandle).first;
+        Pair<Integer, SoundTriggerHwCallback> loadResult = loadGenericModel(module, hwHandle);
+        int handle = loadResult.first;
+        SoundTriggerHwCallback hwCallback = loadResult.second;
 
         // Initiate a recognition.
         startRecognition(module, handle, hwHandle);
 
         // Abort.
-        mService.setCaptureState(true);
+        hwCallback.sendRecognitionEvent(hwHandle, RecognitionStatus.ABORTED);
 
         ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
                 RecognitionEvent.class);
-        verify(callback).onRecognition(eq(handle), eventCaptor.capture());
+        verify(callback).onRecognition(eq(handle), eventCaptor.capture(), eq(101));
 
         // Validate the event.
         assertEquals(RecognitionStatus.ABORTED, eventCaptor.getValue().status);
 
-        // Make sure we are notified of the lost availability.
-        verify(callback).onRecognitionAvailabilityChange(false);
-
-        // Attempt to start a new recognition - should get an abort event immediately, without
-        // involving the HAL.
-        clearInvocations(callback);
-        clearInvocations(mHalDriver);
-        module.startRecognition(handle, createRecognitionConfig());
-        verify(callback).onRecognition(eq(handle), eventCaptor.capture());
-        assertEquals(RecognitionStatus.ABORTED, eventCaptor.getValue().status);
-        verifyNotStartRecognition();
-
-        // Now enable it and make sure we are notified.
-        mService.setCaptureState(false);
-        verify(callback).onRecognitionAvailabilityChange(true);
-
         // Unload the model.
         unloadModel(module, handle, hwHandle);
         module.detach();
@@ -1154,298 +501,124 @@
     @Test
     public void testAbortPhraseRecognition() throws Exception {
         // Make sure the HAL doesn't support concurrent capture.
-        initService(false);
-        mService.setCaptureState(false);
-
         ISoundTriggerCallback callback = createCallbackMock();
         ISoundTriggerModule module = mService.attach(0, callback);
-        verify(callback).onRecognitionAvailabilityChange(true);
 
         // Load the model.
         final int hwHandle = 11;
-        int handle = loadPhraseModel(module, hwHandle).first;
+        Pair<Integer, SoundTriggerHwCallback> loadResult = loadPhraseModel(module, hwHandle);
+        int handle = loadResult.first;
+        SoundTriggerHwCallback hwCallback = loadResult.second;
 
         // Initiate a recognition.
         startRecognition(module, handle, hwHandle);
 
         // Abort.
-        mService.setCaptureState(true);
+        hwCallback.sendPhraseRecognitionEvent(hwHandle, RecognitionStatus.ABORTED);
 
         ArgumentCaptor<PhraseRecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
                 PhraseRecognitionEvent.class);
-        verify(callback).onPhraseRecognition(eq(handle), eventCaptor.capture());
+        verify(callback).onPhraseRecognition(eq(handle), eventCaptor.capture(), eq(101));
 
         // Validate the event.
         assertEquals(RecognitionStatus.ABORTED, eventCaptor.getValue().common.status);
 
-        // Make sure we are notified of the lost availability.
-        verify(callback).onRecognitionAvailabilityChange(false);
-
-        // Attempt to start a new recognition - should get an abort event immediately, without
-        // involving the HAL.
-        clearInvocations(callback);
-        clearInvocations(mHalDriver);
-        module.startRecognition(handle, createRecognitionConfig());
-        verify(callback).onPhraseRecognition(eq(handle), eventCaptor.capture());
-        assertEquals(RecognitionStatus.ABORTED, eventCaptor.getValue().common.status);
-        verifyNotStartRecognition();
-
-        // Now enable it and make sure we are notified.
-        mService.setCaptureState(false);
-        verify(callback).onRecognitionAvailabilityChange(true);
-
         // Unload the model.
         unloadModel(module, handle, hwHandle);
         module.detach();
     }
 
     @Test
-    public void testNotAbortRecognitionConcurrent() throws Exception {
-        // Make sure the HAL supports concurrent capture.
-        initService(true);
-
-        ISoundTriggerCallback callback = createCallbackMock();
-        ISoundTriggerModule module = mService.attach(0, callback);
-        verify(callback).onRecognitionAvailabilityChange(true);
-        clearInvocations(callback);
-
-        // Load the model.
-        final int hwHandle = 13;
-        int handle = loadGenericModel(module, hwHandle).first;
-
-        // Initiate a recognition.
-        startRecognition(module, handle, hwHandle);
-
-        // Signal concurrent capture. Shouldn't abort.
-        mService.setCaptureState(true);
-        verify(callback, never()).onRecognition(anyInt(), any());
-        verify(callback, never()).onRecognitionAvailabilityChange(anyBoolean());
-
-        // Stop the recognition.
-        stopRecognition(module, handle, hwHandle);
-
-        // Initiating a new one should work fine.
-        clearInvocations(mHalDriver);
-        startRecognition(module, handle, hwHandle);
-        verify(callback, never()).onRecognition(anyInt(), any());
-        stopRecognition(module, handle, hwHandle);
-
-        // Unload the model.
-        module.unloadModel(handle);
-        module.detach();
-    }
-
-    @Test
-    public void testNotAbortPhraseRecognitionConcurrent() throws Exception {
-        // Make sure the HAL supports concurrent capture.
-        initService(true);
-
-        ISoundTriggerCallback callback = createCallbackMock();
-        ISoundTriggerModule module = mService.attach(0, callback);
-        verify(callback).onRecognitionAvailabilityChange(true);
-        clearInvocations(callback);
-
-        // Load the model.
-        final int hwHandle = 13;
-        int handle = loadPhraseModel(module, hwHandle).first;
-
-        // Initiate a recognition.
-        startRecognition(module, handle, hwHandle);
-
-        // Signal concurrent capture. Shouldn't abort.
-        mService.setCaptureState(true);
-        verify(callback, never()).onPhraseRecognition(anyInt(), any());
-        verify(callback, never()).onRecognitionAvailabilityChange(anyBoolean());
-
-        // Stop the recognition.
-        stopRecognition(module, handle, hwHandle);
-
-        // Initiating a new one should work fine.
-        clearInvocations(mHalDriver);
-        startRecognition(module, handle, hwHandle);
-        verify(callback, never()).onRecognition(anyInt(), any());
-        stopRecognition(module, handle, hwHandle);
-
-        // Unload the model.
-        module.unloadModel(handle);
-        module.detach();
-    }
-
-    @Test
     public void testParameterSupported() throws Exception {
-        if (!(mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw)) {
-            return;
-        }
-
-        android.hardware.soundtrigger.V2_3.ISoundTriggerHw driver =
-                (android.hardware.soundtrigger.V2_3.ISoundTriggerHw) mHalDriver;
-
-        initService(false);
         ISoundTriggerCallback callback = createCallbackMock();
         ISoundTriggerModule module = mService.attach(0, callback);
         final int hwHandle = 12;
         int modelHandle = loadGenericModel(module, hwHandle).first;
 
-        doAnswer((Answer<Void>) invocation -> {
-            android.hardware.soundtrigger.V2_3.ISoundTriggerHw.queryParameterCallback
-                    resultCallback = invocation.getArgument(2);
-            android.hardware.soundtrigger.V2_3.ModelParameterRange range =
-                    new android.hardware.soundtrigger.V2_3.ModelParameterRange();
-            range.start = 23;
-            range.end = 45;
-            OptionalModelParameterRange optionalRange = new OptionalModelParameterRange();
-            optionalRange.range(range);
-            resultCallback.onValues(0, optionalRange);
-            return null;
-        }).when(driver).queryParameter(eq(hwHandle),
-                eq(android.hardware.soundtrigger.V2_3.ModelParameter.THRESHOLD_FACTOR), any());
+        ModelParameterRange halRange = new ModelParameterRange();
+        halRange.minInclusive = 23;
+        halRange.maxInclusive = 45;
+
+        when(mHalDriver.queryParameter(eq(hwHandle),
+                eq(ModelParameter.THRESHOLD_FACTOR))).thenReturn(halRange);
 
         ModelParameterRange range = module.queryModelParameterSupport(modelHandle,
                 ModelParameter.THRESHOLD_FACTOR);
 
-        verify(driver).queryParameter(eq(hwHandle),
-                eq(android.hardware.soundtrigger.V2_3.ModelParameter.THRESHOLD_FACTOR), any());
+        verify(mHalDriver).queryParameter(eq(hwHandle), eq(ModelParameter.THRESHOLD_FACTOR));
 
-        assertEquals(23, range.minInclusive);
-        assertEquals(45, range.maxInclusive);
-    }
-
-    @Test
-    public void testParameterNotSupportedOld() throws Exception {
-        if (mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw) {
-            return;
-        }
-
-        initService(false);
-        ISoundTriggerCallback callback = createCallbackMock();
-        ISoundTriggerModule module = mService.attach(0, callback);
-        final int hwHandle = 13;
-        int modelHandle = loadGenericModel(module, hwHandle).first;
-
-        ModelParameterRange range = module.queryModelParameterSupport(modelHandle,
-                ModelParameter.THRESHOLD_FACTOR);
-
-        assertNull(range);
+        assertEquals(halRange, range);
     }
 
     @Test
     public void testParameterNotSupported() throws Exception {
-        if (!(mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw)) {
-            return;
-        }
-
-        android.hardware.soundtrigger.V2_3.ISoundTriggerHw driver =
-                (android.hardware.soundtrigger.V2_3.ISoundTriggerHw) mHalDriver;
-
-        initService(false);
         ISoundTriggerCallback callback = createCallbackMock();
         ISoundTriggerModule module = mService.attach(0, callback);
         final int hwHandle = 13;
         int modelHandle = loadGenericModel(module, hwHandle).first;
 
-        doAnswer(invocation -> {
-            android.hardware.soundtrigger.V2_3.ISoundTriggerHw.queryParameterCallback
-                    resultCallback = invocation.getArgument(2);
-            // This is the return of this method.
-            resultCallback.onValues(0, new OptionalModelParameterRange());
-            return null;
-        }).when(driver).queryParameter(eq(hwHandle),
-                eq(android.hardware.soundtrigger.V2_3.ModelParameter.THRESHOLD_FACTOR), any());
+        when(mHalDriver.queryParameter(eq(hwHandle),
+                eq(ModelParameter.THRESHOLD_FACTOR))).thenReturn(null);
 
         ModelParameterRange range = module.queryModelParameterSupport(modelHandle,
                 ModelParameter.THRESHOLD_FACTOR);
 
-        verify(driver).queryParameter(eq(hwHandle),
-                eq(android.hardware.soundtrigger.V2_3.ModelParameter.THRESHOLD_FACTOR), any());
+        verify(mHalDriver).queryParameter(eq(hwHandle), eq(ModelParameter.THRESHOLD_FACTOR));
 
         assertNull(range);
     }
 
     @Test
     public void testGetParameter() throws Exception {
-        if (!(mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw)) {
-            return;
-        }
-
-        android.hardware.soundtrigger.V2_3.ISoundTriggerHw driver =
-                (android.hardware.soundtrigger.V2_3.ISoundTriggerHw) mHalDriver;
-
-        initService(false);
         ISoundTriggerCallback callback = createCallbackMock();
         ISoundTriggerModule module = mService.attach(0, callback);
         final int hwHandle = 14;
         int modelHandle = loadGenericModel(module, hwHandle).first;
 
-        doAnswer(invocation -> {
-            android.hardware.soundtrigger.V2_3.ISoundTriggerHw.getParameterCallback
-                    resultCallback = invocation.getArgument(2);
-            // This is the return of this method.
-            resultCallback.onValues(0, 234);
-            return null;
-        }).when(driver).getParameter(eq(hwHandle),
-                eq(android.hardware.soundtrigger.V2_3.ModelParameter.THRESHOLD_FACTOR), any());
+        when(mHalDriver.getModelParameter(hwHandle, ModelParameter.THRESHOLD_FACTOR)).thenReturn(
+                234);
 
         int value = module.getModelParameter(modelHandle, ModelParameter.THRESHOLD_FACTOR);
 
-        verify(driver).getParameter(eq(hwHandle),
-                eq(android.hardware.soundtrigger.V2_3.ModelParameter.THRESHOLD_FACTOR), any());
+        verify(mHalDriver).getModelParameter(hwHandle, ModelParameter.THRESHOLD_FACTOR);
 
         assertEquals(234, value);
     }
 
     @Test
     public void testSetParameter() throws Exception {
-        if (!(mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw)) {
-            return;
-        }
-
-        android.hardware.soundtrigger.V2_3.ISoundTriggerHw driver =
-                (android.hardware.soundtrigger.V2_3.ISoundTriggerHw) mHalDriver;
-
-        initService(false);
         ISoundTriggerCallback callback = createCallbackMock();
         ISoundTriggerModule module = mService.attach(0, callback);
         final int hwHandle = 17;
         int modelHandle = loadGenericModel(module, hwHandle).first;
 
-        when(driver.setParameter(hwHandle,
-                android.hardware.soundtrigger.V2_3.ModelParameter.THRESHOLD_FACTOR,
-                456)).thenReturn(0);
-
         module.setModelParameter(modelHandle, ModelParameter.THRESHOLD_FACTOR, 456);
 
-        verify(driver).setParameter(hwHandle,
-                android.hardware.soundtrigger.V2_3.ModelParameter.THRESHOLD_FACTOR, 456);
+        verify(mHalDriver).setModelParameter(hwHandle, ModelParameter.THRESHOLD_FACTOR, 456);
     }
 
     private static class SoundTriggerHwCallback {
-        private final android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback mCallback;
-        private final int mCookie;
+        private final ISoundTriggerHal.ModelCallback mCallback;
 
-        SoundTriggerHwCallback(android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback callback,
-                int cookie) {
+        SoundTriggerHwCallback(ISoundTriggerHal.ModelCallback callback) {
             mCallback = callback;
-            mCookie = cookie;
         }
 
-        private void sendRecognitionEvent(int hwHandle, int status) throws RemoteException {
-            if (mCallback instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback) {
-                ((android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback) mCallback).recognitionCallback_2_1(
-                        createRecognitionEvent_2_1(hwHandle, status), mCookie);
-            } else {
-                mCallback.recognitionCallback(createRecognitionEvent_2_0(hwHandle, status),
-                        mCookie);
-            }
+        private RecognitionEvent sendRecognitionEvent(int hwHandle, @RecognitionStatus int status) {
+            RecognitionEvent event = TestUtil.createRecognitionEvent(status);
+            mCallback.recognitionCallback(hwHandle, event);
+            return event;
         }
 
-        private void sendPhraseRecognitionEvent(int hwHandle, int status) throws RemoteException {
-            if (mCallback instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback) {
-                ((android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback) mCallback).phraseRecognitionCallback_2_1(
-                        createPhraseRecognitionEvent_2_1(hwHandle, status), mCookie);
-            } else {
-                mCallback.phraseRecognitionCallback(
-                        createPhraseRecognitionEvent_2_0(hwHandle, status), mCookie);
-            }
+        private PhraseRecognitionEvent sendPhraseRecognitionEvent(int hwHandle,
+                @RecognitionStatus int status) {
+            PhraseRecognitionEvent event = TestUtil.createPhraseRecognitionEvent(status);
+            mCallback.phraseRecognitionCallback(hwHandle, event);
+            return event;
+        }
+
+        private void sendUnloadEvent(int hwHandle) {
+            mCallback.modelUnloaded(hwHandle);
         }
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/TestUtil.java b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/TestUtil.java
new file mode 100644
index 0000000..4eca298
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/TestUtil.java
@@ -0,0 +1,446 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.soundtrigger_middleware;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.annotation.NonNull;
+import android.hardware.soundtrigger.V2_1.ISoundTriggerHw;
+import android.hardware.soundtrigger.V2_4.ISoundTriggerHwCallback;
+import android.media.audio.common.AudioChannelMask;
+import android.media.audio.common.AudioConfig;
+import android.media.audio.common.AudioFormat;
+import android.media.soundtrigger.AudioCapabilities;
+import android.media.soundtrigger.ConfidenceLevel;
+import android.media.soundtrigger.Phrase;
+import android.media.soundtrigger.PhraseRecognitionEvent;
+import android.media.soundtrigger.PhraseRecognitionExtra;
+import android.media.soundtrigger.PhraseSoundModel;
+import android.media.soundtrigger.Properties;
+import android.media.soundtrigger.RecognitionConfig;
+import android.media.soundtrigger.RecognitionEvent;
+import android.media.soundtrigger.RecognitionMode;
+import android.media.soundtrigger.RecognitionStatus;
+import android.media.soundtrigger.SoundModel;
+import android.media.soundtrigger.SoundModelType;
+import android.os.HidlMemoryUtil;
+import android.os.ParcelFileDescriptor;
+import android.os.SharedMemory;
+import android.system.ErrnoException;
+
+import java.io.FileDescriptor;
+import java.nio.ByteBuffer;
+import java.util.List;
+
+/**
+ * Test utilities, aimed at generating populated objects of the various types and validating
+ * corresponding objects generated by the system under test.
+ */
+class TestUtil {
+    static SoundModel createGenericSoundModel() {
+        return createSoundModel(SoundModelType.GENERIC);
+    }
+
+    private static SoundModel createSoundModel(@SoundModelType int type) {
+        SoundModel model = new SoundModel();
+        model.type = type;
+        model.uuid = "12345678-2345-3456-4567-abcdef987654";
+        model.vendorUuid = "87654321-5432-6543-7654-456789fedcba";
+        byte[] data = new byte[]{91, 92, 93, 94, 95};
+        model.data = new ParcelFileDescriptor(byteArrayToFileDescriptor(data));
+        model.dataSize = data.length;
+        return model;
+    }
+
+    private static void validateSoundModel_2_1(ISoundTriggerHw.SoundModel model, int type) {
+        assertEquals(type, model.header.type);
+        assertEquals("12345678-2345-3456-4567-abcdef987654",
+                ConversionUtil.hidl2aidlUuid(model.header.uuid));
+        assertEquals("87654321-5432-6543-7654-456789fedcba",
+                ConversionUtil.hidl2aidlUuid(model.header.vendorUuid));
+        assertArrayEquals(new byte[]{91, 92, 93, 94, 95},
+                HidlMemoryUtil.hidlMemoryToByteArray(model.data));
+    }
+
+    private static void validateSoundModel_2_0(
+            android.hardware.soundtrigger.V2_0.ISoundTriggerHw.SoundModel model, int type) {
+        assertEquals(type, model.type);
+        assertEquals("12345678-2345-3456-4567-abcdef987654",
+                ConversionUtil.hidl2aidlUuid(model.uuid));
+        assertEquals("87654321-5432-6543-7654-456789fedcba",
+                ConversionUtil.hidl2aidlUuid(model.vendorUuid));
+        assertArrayEquals(new Byte[]{91, 92, 93, 94, 95}, model.data.toArray());
+    }
+
+    static void validateGenericSoundModel_2_1(ISoundTriggerHw.SoundModel model) {
+        validateSoundModel_2_1(model, android.hardware.soundtrigger.V2_0.SoundModelType.GENERIC);
+    }
+
+    static void validateGenericSoundModel_2_0(
+            android.hardware.soundtrigger.V2_0.ISoundTriggerHw.SoundModel model) {
+        validateSoundModel_2_0(model, android.hardware.soundtrigger.V2_0.SoundModelType.GENERIC);
+    }
+
+    static PhraseSoundModel createPhraseSoundModel() {
+        PhraseSoundModel model = new PhraseSoundModel();
+        model.common = createSoundModel(SoundModelType.KEYPHRASE);
+        model.phrases = new Phrase[1];
+        model.phrases[0] = new Phrase();
+        model.phrases[0].id = 123;
+        model.phrases[0].users = new int[]{5, 6, 7};
+        model.phrases[0].locale = "locale";
+        model.phrases[0].text = "text";
+        model.phrases[0].recognitionModes =
+                RecognitionMode.USER_AUTHENTICATION | RecognitionMode.USER_IDENTIFICATION;
+        return model;
+    }
+
+    static void validatePhraseSoundModel_2_1(ISoundTriggerHw.PhraseSoundModel model) {
+        validateSoundModel_2_1(model.common,
+                android.hardware.soundtrigger.V2_0.SoundModelType.KEYPHRASE);
+        validatePhrases_2_0(model.phrases);
+    }
+
+    static void validatePhraseSoundModel_2_0(
+            android.hardware.soundtrigger.V2_0.ISoundTriggerHw.PhraseSoundModel model) {
+        validateSoundModel_2_0(model.common,
+                android.hardware.soundtrigger.V2_0.SoundModelType.KEYPHRASE);
+        validatePhrases_2_0(model.phrases);
+    }
+
+    private static void validatePhrases_2_0(
+            List<android.hardware.soundtrigger.V2_0.ISoundTriggerHw.Phrase> phrases) {
+        assertEquals(1, phrases.size());
+        assertEquals(123, phrases.get(0).id);
+        assertArrayEquals(new Integer[]{5, 6, 7}, phrases.get(0).users.toArray());
+        assertEquals("locale", phrases.get(0).locale);
+        assertEquals("text", phrases.get(0).text);
+        assertEquals(RecognitionMode.USER_AUTHENTICATION | RecognitionMode.USER_IDENTIFICATION,
+                phrases.get(0).recognitionModes);
+    }
+
+    static android.hardware.soundtrigger.V2_0.ISoundTriggerHw.Properties createDefaultProperties_2_0(
+            boolean supportConcurrentCapture) {
+        android.hardware.soundtrigger.V2_0.ISoundTriggerHw.Properties properties =
+                new android.hardware.soundtrigger.V2_0.ISoundTriggerHw.Properties();
+        properties.implementor = "implementor";
+        properties.description = "description";
+        properties.version = 123;
+        properties.uuid.timeLow = 1;
+        properties.uuid.timeMid = 2;
+        properties.uuid.versionAndTimeHigh = 3;
+        properties.uuid.variantAndClockSeqHigh = 4;
+        properties.uuid.node = new byte[]{5, 6, 7, 8, 9, 10};
+
+        properties.maxSoundModels = 456;
+        properties.maxKeyPhrases = 567;
+        properties.maxUsers = 678;
+        properties.recognitionModes =
+                android.hardware.soundtrigger.V2_0.RecognitionMode.VOICE_TRIGGER
+                        | android.hardware.soundtrigger.V2_0.RecognitionMode.USER_IDENTIFICATION
+                        | android.hardware.soundtrigger.V2_0.RecognitionMode.USER_AUTHENTICATION
+                        | android.hardware.soundtrigger.V2_0.RecognitionMode.GENERIC_TRIGGER;
+        properties.captureTransition = true;
+        properties.maxBufferMs = 321;
+        properties.concurrentCapture = supportConcurrentCapture;
+        properties.triggerInEvent = true;
+        properties.powerConsumptionMw = 432;
+        return properties;
+    }
+
+    static android.hardware.soundtrigger.V2_3.Properties createDefaultProperties_2_3(
+            boolean supportConcurrentCapture) {
+        android.hardware.soundtrigger.V2_3.Properties properties =
+                new android.hardware.soundtrigger.V2_3.Properties();
+        properties.base = createDefaultProperties_2_0(supportConcurrentCapture);
+        properties.supportedModelArch = "supportedModelArch";
+        properties.audioCapabilities =
+                android.hardware.soundtrigger.V2_3.AudioCapabilities.ECHO_CANCELLATION
+                        | android.hardware.soundtrigger.V2_3.AudioCapabilities.NOISE_SUPPRESSION;
+        return properties;
+    }
+
+    static Properties createDefaultProperties(boolean supportConcurrentCapture) {
+        Properties properties = new Properties();
+        properties.implementor = "implementor";
+        properties.description = "description";
+        properties.version = 123;
+        properties.uuid = "00000001-0002-0003-0004-05060708090a";
+        properties.maxSoundModels = 456;
+        properties.maxKeyPhrases = 567;
+        properties.maxUsers = 678;
+        properties.recognitionModes =
+                RecognitionMode.VOICE_TRIGGER
+                        | RecognitionMode.USER_IDENTIFICATION
+                        | RecognitionMode.USER_AUTHENTICATION
+                        | RecognitionMode.GENERIC_TRIGGER;
+        properties.captureTransition = true;
+        properties.maxBufferMs = 321;
+        properties.concurrentCapture = supportConcurrentCapture;
+        properties.triggerInEvent = true;
+        properties.powerConsumptionMw = 432;
+        properties.supportedModelArch = "supportedModelArch";
+        properties.audioCapabilities = AudioCapabilities.ECHO_CANCELLATION
+                | AudioCapabilities.NOISE_SUPPRESSION;
+        return properties;
+    }
+
+    static void validateDefaultProperties(Properties properties,
+            boolean supportConcurrentCapture) {
+        validateDefaultProperties(properties, supportConcurrentCapture,
+                AudioCapabilities.ECHO_CANCELLATION | AudioCapabilities.NOISE_SUPPRESSION,
+                "supportedModelArch");
+    }
+
+    static void validateDefaultProperties(Properties properties,
+            boolean supportConcurrentCapture, @AudioCapabilities int audioCapabilities,
+            @NonNull String supportedModelArch) {
+        assertEquals("implementor", properties.implementor);
+        assertEquals("description", properties.description);
+        assertEquals(123, properties.version);
+        assertEquals("00000001-0002-0003-0004-05060708090a", properties.uuid);
+        assertEquals(456, properties.maxSoundModels);
+        assertEquals(567, properties.maxKeyPhrases);
+        assertEquals(678, properties.maxUsers);
+        assertEquals(RecognitionMode.GENERIC_TRIGGER
+                | RecognitionMode.USER_AUTHENTICATION
+                | RecognitionMode.USER_IDENTIFICATION
+                | RecognitionMode.VOICE_TRIGGER, properties.recognitionModes);
+        assertTrue(properties.captureTransition);
+        assertEquals(321, properties.maxBufferMs);
+        assertEquals(supportConcurrentCapture, properties.concurrentCapture);
+        assertTrue(properties.triggerInEvent);
+        assertEquals(432, properties.powerConsumptionMw);
+        assertEquals(supportedModelArch, properties.supportedModelArch);
+        assertEquals(audioCapabilities, properties.audioCapabilities);
+    }
+
+    static RecognitionConfig createRecognitionConfig() {
+        RecognitionConfig config = new RecognitionConfig();
+        config.captureRequested = true;
+        config.phraseRecognitionExtras = new PhraseRecognitionExtra[]{new PhraseRecognitionExtra()};
+        config.phraseRecognitionExtras[0].id = 123;
+        config.phraseRecognitionExtras[0].confidenceLevel = 4;
+        config.phraseRecognitionExtras[0].recognitionModes = 5;
+        config.phraseRecognitionExtras[0].levels = new ConfidenceLevel[]{new ConfidenceLevel()};
+        config.phraseRecognitionExtras[0].levels[0].userId = 234;
+        config.phraseRecognitionExtras[0].levels[0].levelPercent = 34;
+        config.data = new byte[]{5, 4, 3, 2, 1};
+        config.audioCapabilities = AudioCapabilities.ECHO_CANCELLATION
+                | AudioCapabilities.NOISE_SUPPRESSION;
+        return config;
+    }
+
+    static void validateRecognitionConfig_2_0(
+            android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig config,
+            int captureDevice, int captureHandle) {
+        assertTrue(config.captureRequested);
+        assertEquals(captureDevice, config.captureDevice);
+        assertEquals(captureHandle, config.captureHandle);
+        assertEquals(1, config.phrases.size());
+        android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra halPhraseExtra =
+                config.phrases.get(0);
+        assertEquals(123, halPhraseExtra.id);
+        assertEquals(4, halPhraseExtra.confidenceLevel);
+        assertEquals(5, halPhraseExtra.recognitionModes);
+        assertEquals(1, halPhraseExtra.levels.size());
+        android.hardware.soundtrigger.V2_0.ConfidenceLevel halLevel = halPhraseExtra.levels.get(0);
+        assertEquals(234, halLevel.userId);
+        assertEquals(34, halLevel.levelPercent);
+        assertArrayEquals(new Byte[]{5, 4, 3, 2, 1}, config.data.toArray());
+    }
+
+    static void validateRecognitionConfig_2_1(
+            android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig config,
+            int captureDevice, int captureHandle) {
+        assertTrue(config.header.captureRequested);
+        assertEquals(captureDevice, config.header.captureDevice);
+        assertEquals(captureHandle, config.header.captureHandle);
+        assertEquals(1, config.header.phrases.size());
+        android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra halPhraseExtra =
+                config.header.phrases.get(0);
+        assertEquals(123, halPhraseExtra.id);
+        assertEquals(4, halPhraseExtra.confidenceLevel);
+        assertEquals(5, halPhraseExtra.recognitionModes);
+        assertEquals(1, halPhraseExtra.levels.size());
+        android.hardware.soundtrigger.V2_0.ConfidenceLevel halLevel = halPhraseExtra.levels.get(0);
+        assertEquals(234, halLevel.userId);
+        assertEquals(34, halLevel.levelPercent);
+        assertArrayEquals(new byte[]{5, 4, 3, 2, 1},
+                HidlMemoryUtil.hidlMemoryToByteArray(config.data));
+    }
+
+    static void validateRecognitionConfig_2_3(
+            android.hardware.soundtrigger.V2_3.RecognitionConfig config, int captureDevice,
+            int captureHandle) {
+        validateRecognitionConfig_2_1(config.base, captureDevice, captureHandle);
+
+        assertEquals(AudioCapabilities.ECHO_CANCELLATION
+                | AudioCapabilities.NOISE_SUPPRESSION, config.audioCapabilities);
+    }
+
+    static android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionEvent createRecognitionEvent_2_0(
+            int hwHandle,
+            int status) {
+        android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionEvent halEvent =
+                new android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionEvent();
+        halEvent.status = status;
+        halEvent.type = SoundModelType.GENERIC;
+        halEvent.model = hwHandle;
+        halEvent.captureAvailable = true;
+        // This field is ignored.
+        halEvent.captureSession = 9999;
+        halEvent.captureDelayMs = 234;
+        halEvent.capturePreambleMs = 345;
+        halEvent.triggerInData = true;
+        halEvent.audioConfig.sampleRateHz = 456;
+        halEvent.audioConfig.channelMask = AudioChannelMask.IN_LEFT;
+        halEvent.audioConfig.format = AudioFormat.MP3;
+        // hwEvent.audioConfig.offloadInfo is irrelevant.
+        halEvent.data.add((byte) 31);
+        halEvent.data.add((byte) 32);
+        halEvent.data.add((byte) 33);
+        return halEvent;
+    }
+
+    static RecognitionEvent createRecognitionEvent(@RecognitionStatus int status) {
+        RecognitionEvent event = new RecognitionEvent();
+        event.status = status;
+        event.type = SoundModelType.GENERIC;
+        event.captureAvailable = true;
+        event.captureDelayMs = 234;
+        event.capturePreambleMs = 345;
+        event.triggerInData = true;
+        event.audioConfig = new AudioConfig();
+        event.audioConfig.sampleRateHz = 456;
+        event.audioConfig.channelMask = AudioChannelMask.IN_LEFT;
+        event.audioConfig.format = AudioFormat.MP3;
+        //event.audioConfig.offloadInfo is irrelevant.
+        event.data = new byte[]{31, 32, 33};
+        return event;
+    }
+
+    static ISoundTriggerHwCallback.RecognitionEvent createRecognitionEvent_2_1(
+            int hwHandle,
+            int status) {
+        ISoundTriggerHwCallback.RecognitionEvent halEvent =
+                new ISoundTriggerHwCallback.RecognitionEvent();
+        halEvent.header = createRecognitionEvent_2_0(hwHandle, status);
+        halEvent.header.data.clear();
+        halEvent.data = HidlMemoryUtil.byteArrayToHidlMemory(new byte[]{31, 32, 33});
+        return halEvent;
+    }
+
+    static void validateRecognitionEvent(RecognitionEvent event, @RecognitionStatus int status) {
+        assertEquals(status, event.status);
+        assertEquals(SoundModelType.GENERIC, event.type);
+        assertTrue(event.captureAvailable);
+        assertEquals(234, event.captureDelayMs);
+        assertEquals(345, event.capturePreambleMs);
+        assertTrue(event.triggerInData);
+        assertEquals(456, event.audioConfig.sampleRateHz);
+        assertEquals(AudioChannelMask.IN_LEFT, event.audioConfig.channelMask);
+        assertEquals(AudioFormat.MP3, event.audioConfig.format);
+        assertArrayEquals(new byte[]{31, 32, 33}, event.data);
+    }
+
+    static PhraseRecognitionEvent createPhraseRecognitionEvent(@RecognitionStatus int status) {
+        PhraseRecognitionEvent event = new PhraseRecognitionEvent();
+        event.common = createRecognitionEvent(status);
+
+        PhraseRecognitionExtra extra = new PhraseRecognitionExtra();
+        extra.id = 123;
+        extra.confidenceLevel = 52;
+        extra.recognitionModes = RecognitionMode.VOICE_TRIGGER
+                | RecognitionMode.GENERIC_TRIGGER;
+        ConfidenceLevel level = new ConfidenceLevel();
+        level.userId = 31;
+        level.levelPercent = 43;
+        extra.levels = new ConfidenceLevel[]{level};
+        event.phraseExtras = new PhraseRecognitionExtra[]{extra};
+        return event;
+    }
+
+    static android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.PhraseRecognitionEvent
+    createPhraseRecognitionEvent_2_0(int hwHandle, int status) {
+        android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.PhraseRecognitionEvent halEvent =
+                new android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.PhraseRecognitionEvent();
+        halEvent.common = createRecognitionEvent_2_0(hwHandle, status);
+
+        android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra halExtra =
+                new android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra();
+        halExtra.id = 123;
+        halExtra.confidenceLevel = 52;
+        halExtra.recognitionModes = android.hardware.soundtrigger.V2_0.RecognitionMode.VOICE_TRIGGER
+                | android.hardware.soundtrigger.V2_0.RecognitionMode.GENERIC_TRIGGER;
+        android.hardware.soundtrigger.V2_0.ConfidenceLevel halLevel =
+                new android.hardware.soundtrigger.V2_0.ConfidenceLevel();
+        halLevel.userId = 31;
+        halLevel.levelPercent = 43;
+        halExtra.levels.add(halLevel);
+        halEvent.phraseExtras.add(halExtra);
+        return halEvent;
+    }
+
+    static ISoundTriggerHwCallback.PhraseRecognitionEvent createPhraseRecognitionEvent_2_1(
+            int hwHandle, int status) {
+        ISoundTriggerHwCallback.PhraseRecognitionEvent halEvent =
+                new ISoundTriggerHwCallback.PhraseRecognitionEvent();
+        halEvent.common = createRecognitionEvent_2_1(hwHandle, status);
+
+        android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra halExtra =
+                new android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra();
+        halExtra.id = 123;
+        halExtra.confidenceLevel = 52;
+        halExtra.recognitionModes = android.hardware.soundtrigger.V2_0.RecognitionMode.VOICE_TRIGGER
+                | android.hardware.soundtrigger.V2_0.RecognitionMode.GENERIC_TRIGGER;
+        android.hardware.soundtrigger.V2_0.ConfidenceLevel halLevel =
+                new android.hardware.soundtrigger.V2_0.ConfidenceLevel();
+        halLevel.userId = 31;
+        halLevel.levelPercent = 43;
+        halExtra.levels.add(halLevel);
+        halEvent.phraseExtras.add(halExtra);
+        return halEvent;
+    }
+
+    static void validatePhraseRecognitionEvent(PhraseRecognitionEvent event,
+            @RecognitionStatus int status) {
+        validateRecognitionEvent(event.common, status);
+
+        assertEquals(1, event.phraseExtras.length);
+        assertEquals(123, event.phraseExtras[0].id);
+        assertEquals(52, event.phraseExtras[0].confidenceLevel);
+        assertEquals(RecognitionMode.VOICE_TRIGGER | RecognitionMode.GENERIC_TRIGGER,
+                event.phraseExtras[0].recognitionModes);
+        assertEquals(1, event.phraseExtras[0].levels.length);
+        assertEquals(31, event.phraseExtras[0].levels[0].userId);
+        assertEquals(43, event.phraseExtras[0].levels[0].levelPercent);
+    }
+
+    private static FileDescriptor byteArrayToFileDescriptor(byte[] data) {
+        try {
+            SharedMemory shmem = SharedMemory.create("", data.length);
+            ByteBuffer buffer = shmem.mapReadWrite();
+            buffer.put(data);
+            return shmem.getFileDescriptor();
+        } catch (ErrnoException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java
index aadab6e..2f36c7f 100644
--- a/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java
@@ -365,13 +365,13 @@
         mTunerResourceManagerService.registerClientProfileInternal(
                 profiles[0], listener, clientId0);
         assertThat(clientId0[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
-        mTunerResourceManagerService.getClientProfile(clientId0[0])
-                .setPriority(clientPriorities[0]);
+        mTunerResourceManagerService.updateClientPriorityInternal(
+                clientId0[0], clientPriorities[0], 0/*niceValue*/);
         mTunerResourceManagerService.registerClientProfileInternal(
                 profiles[1], new TestResourcesReclaimListener(), clientId1);
         assertThat(clientId1[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
-        mTunerResourceManagerService.getClientProfile(clientId1[0])
-                .setPriority(clientPriorities[1]);
+        mTunerResourceManagerService.updateClientPriorityInternal(
+                clientId1[0], clientPriorities[1], 0/*niceValue*/);
 
         // Init frontend resources.
         TunerFrontendInfo[] infos = new TunerFrontendInfo[2];
@@ -415,13 +415,13 @@
         mTunerResourceManagerService.registerClientProfileInternal(
                 profiles[0], listener, clientId0);
         assertThat(clientId0[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
-        mTunerResourceManagerService.getClientProfile(clientId0[0])
-                .setPriority(clientPriorities[0]);
+        mTunerResourceManagerService.updateClientPriorityInternal(
+                clientId0[0], clientPriorities[0], 0/*niceValue*/);
         mTunerResourceManagerService.registerClientProfileInternal(
                 profiles[1], new TestResourcesReclaimListener(), clientId1);
         assertThat(clientId1[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
-        mTunerResourceManagerService.getClientProfile(clientId1[0])
-                .setPriority(clientPriorities[1]);
+        mTunerResourceManagerService.updateClientPriorityInternal(
+                clientId1[0], clientPriorities[1], 0/*niceValue*/);
 
         // Init frontend resources.
         TunerFrontendInfo[] infos = new TunerFrontendInfo[2];
@@ -511,13 +511,13 @@
         mTunerResourceManagerService.registerClientProfileInternal(
                 profiles[0], listener, clientId0);
         assertThat(clientId0[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
-        mTunerResourceManagerService.getClientProfile(clientId0[0])
-                .setPriority(clientPriorities[0]);
+        mTunerResourceManagerService.updateClientPriorityInternal(
+                clientId0[0], clientPriorities[0], 0/*niceValue*/);
         mTunerResourceManagerService.registerClientProfileInternal(
                 profiles[1], new TestResourcesReclaimListener(), clientId1);
         assertThat(clientId1[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
-        mTunerResourceManagerService.getClientProfile(clientId1[0])
-                .setPriority(clientPriorities[1]);
+        mTunerResourceManagerService.updateClientPriorityInternal(
+                clientId1[0], clientPriorities[1], 0/*niceValue*/);
 
         // Init cas resources.
         mTunerResourceManagerService.updateCasInfoInternal(1 /*casSystemId*/, 2 /*maxSessionNum*/);
@@ -567,13 +567,13 @@
         mTunerResourceManagerService.registerClientProfileInternal(
                 profiles[0], listener, clientId0);
         assertThat(clientId0[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
-        mTunerResourceManagerService.getClientProfile(clientId0[0])
-                .setPriority(clientPriorities[0]);
+        mTunerResourceManagerService.updateClientPriorityInternal(
+                clientId0[0], clientPriorities[0], 0/*niceValue*/);
         mTunerResourceManagerService.registerClientProfileInternal(
                 profiles[1], new TestResourcesReclaimListener(), clientId1);
         assertThat(clientId1[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
-        mTunerResourceManagerService.getClientProfile(clientId1[0])
-                .setPriority(clientPriorities[1]);
+        mTunerResourceManagerService.updateClientPriorityInternal(
+                clientId1[0], clientPriorities[1], 0/*niceValue*/);
 
         // Init cicam/cas resources.
         mTunerResourceManagerService.updateCasInfoInternal(1 /*casSystemId*/, 2 /*maxSessionNum*/);
@@ -697,13 +697,13 @@
         mTunerResourceManagerService.registerClientProfileInternal(
                 profiles[0], listener, clientId0);
         assertThat(clientId0[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
-        mTunerResourceManagerService.getClientProfile(clientId0[0])
-                .setPriority(clientPriorities[0]);
+        mTunerResourceManagerService.updateClientPriorityInternal(
+                clientId0[0], clientPriorities[0], 0/*niceValue*/);
         mTunerResourceManagerService.registerClientProfileInternal(
                 profiles[1], new TestResourcesReclaimListener(), clientId1);
         assertThat(clientId1[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
-        mTunerResourceManagerService.getClientProfile(clientId1[0])
-                .setPriority(clientPriorities[1]);
+        mTunerResourceManagerService.updateClientPriorityInternal(
+                clientId1[0], clientPriorities[1], 0/*niceValue*/);
 
         // Init lnb resources.
         int[] lnbHandles = {1};
diff --git a/services/usage/java/com/android/server/usage/StorageStatsService.java b/services/usage/java/com/android/server/usage/StorageStatsService.java
index a0a3909..61074ba 100644
--- a/services/usage/java/com/android/server/usage/StorageStatsService.java
+++ b/services/usage/java/com/android/server/usage/StorageStatsService.java
@@ -207,7 +207,7 @@
     public boolean isReservedSupported(String volumeUuid, String callingPackage) {
         if (volumeUuid == StorageManager.UUID_PRIVATE_INTERNAL) {
             return SystemProperties.getBoolean(StorageManager.PROP_HAS_RESERVED, false)
-                    || Build.IS_CONTAINER;
+                    || Build.IS_ARC;
         } else {
             return false;
         }
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index 2e692e6..a557491 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -239,8 +239,7 @@
                 mHandler.updateState(state);
             } else if ("GETPROTOCOL".equals(accessory)) {
                 if (DEBUG) Slog.d(TAG, "got accessory get protocol");
-                long elapsedRealtime = SystemClock.elapsedRealtime();
-                mHandler.setAccessoryUEventTime(elapsedRealtime);
+                mHandler.setAccessoryUEventTime(SystemClock.elapsedRealtime());
                 resetAccessoryHandshakeTimeoutHandler();
             } else if ("SENDSTRING".equals(accessory)) {
                 if (DEBUG) Slog.d(TAG, "got accessory send string");
@@ -465,6 +464,8 @@
         }
     }
 
+    //TODO It is not clear that this method serves any purpose (at least on Pixel devices)
+    // consider removing
     private static void initRndisAddress() {
         // configure RNDIS ethernet address based on our serial number using the same algorithm
         // we had been previously using in kernel board files
@@ -484,7 +485,7 @@
         try {
             FileUtils.stringToFile(RNDIS_ETH_ADDR_PATH, addrString);
         } catch (IOException e) {
-            Slog.e(TAG, "failed to write to " + RNDIS_ETH_ADDR_PATH);
+            Slog.i(TAG, "failed to write to " + RNDIS_ETH_ADDR_PATH);
         }
     }
 
@@ -760,9 +761,6 @@
         }
 
         private void broadcastUsbAccessoryHandshake() {
-            long elapsedRealtime = SystemClock.elapsedRealtime();
-            long accessoryHandShakeEnd = elapsedRealtime;
-
             // send a sticky broadcast containing USB accessory handshake information
             Intent intent = new Intent(UsbManager.ACTION_USB_ACCESSORY_HANDSHAKE)
                     .putExtra(UsbManager.EXTRA_ACCESSORY_UEVENT_TIME,
@@ -772,7 +770,7 @@
                     .putExtra(UsbManager.EXTRA_ACCESSORY_START,
                             mStartAccessory)
                     .putExtra(UsbManager.EXTRA_ACCESSORY_HANDSHAKE_END,
-                            accessoryHandShakeEnd);
+                            SystemClock.elapsedRealtime());
 
             if (DEBUG) {
                 Slog.d(TAG, "broadcasting " + intent + " extras: " + intent.getExtras());
@@ -780,7 +778,6 @@
 
             sendStickyBroadcast(intent);
             resetUsbAccessoryHandshakeDebuggingInfo();
-            accessoryHandShakeEnd = 0L;
         }
 
         protected void updateUsbStateBroadcastIfNeeded(long functions) {
@@ -1935,14 +1932,14 @@
                     }
                     break;
                 case MSG_GET_CURRENT_USB_FUNCTIONS:
-                    Slog.e(TAG, "prcessing MSG_GET_CURRENT_USB_FUNCTIONS");
+                    Slog.i(TAG, "processing MSG_GET_CURRENT_USB_FUNCTIONS");
                     mCurrentUsbFunctionsReceived = true;
 
                     if (mCurrentUsbFunctionsRequested) {
-                        Slog.e(TAG, "updating mCurrentFunctions");
+                        Slog.i(TAG, "updating mCurrentFunctions");
                         // Mask out adb, since it is stored in mAdbEnabled
                         mCurrentFunctions = ((Long) msg.obj) & ~UsbManager.FUNCTION_ADB;
-                        Slog.e(TAG,
+                        Slog.i(TAG,
                                 "mCurrentFunctions:" + mCurrentFunctions + "applied:" + msg.arg1);
                         mCurrentFunctionsApplied = msg.arg1 == 1;
                     }
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
index a3b5fc7..be37a91 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
@@ -36,7 +36,6 @@
 import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
 import android.hardware.soundtrigger.SoundTrigger.RecognitionEvent;
 import android.hardware.soundtrigger.SoundTrigger.SoundModel;
-import android.hardware.soundtrigger.SoundTrigger.SoundModelEvent;
 import android.hardware.soundtrigger.SoundTriggerModule;
 import android.os.Binder;
 import android.os.DeadObjectException;
@@ -109,9 +108,6 @@
     private boolean mCallActive = false;
     private @SoundTriggerPowerSaveMode int mSoundTriggerPowerSaveMode =
             PowerManager.SOUND_TRIGGER_MODE_ALL_ENABLED;
-    // Indicates if the native sound trigger service is disabled or not.
-    // This is an indirect indication of the microphone being open in some other application.
-    private boolean mServiceDisabled = false;
 
     // Whether ANY recognition (keyphrase or generic) has been requested.
     private boolean mRecognitionRequested = false;
@@ -862,23 +858,19 @@
     }
 
     @Override
-    public void onSoundModelUpdate(SoundModelEvent event) {
-        if (event == null) {
-            Slog.w(TAG, "Invalid sound model event!");
-            return;
-        }
-        if (DBG) Slog.d(TAG, "onSoundModelUpdate: " + event);
+    public void onModelUnloaded(int modelHandle) {
+        if (DBG) Slog.d(TAG, "onModelUnloaded: " + modelHandle);
         synchronized (mLock) {
             MetricsLogger.count(mContext, "sth_sound_model_updated", 1);
-            onSoundModelUpdatedLocked(event);
+            onModelUnloadedLocked(modelHandle);
         }
     }
 
     @Override
-    public void onServiceStateChange(int state) {
-        if (DBG) Slog.d(TAG, "onServiceStateChange, state: " + state);
+    public void onResourcesAvailable() {
+        if (DBG) Slog.d(TAG, "onResourcesAvailable");
         synchronized (mLock) {
-            onServiceStateChangedLocked(SoundTrigger.SERVICE_STATE_DISABLED == state);
+            onResourcesAvailableLocked();
         }
     }
 
@@ -910,15 +902,14 @@
         updateAllRecognitionsLocked();
     }
 
-    private void onSoundModelUpdatedLocked(SoundModelEvent event) {
-        // TODO: Handle sound model update here.
+    private void onModelUnloadedLocked(int modelHandle) {
+        ModelData modelData = getModelDataForLocked(modelHandle);
+        if (modelData != null) {
+            modelData.setNotLoaded();
+        }
     }
 
-    private void onServiceStateChangedLocked(boolean disabled) {
-        if (disabled == mServiceDisabled) {
-            return;
-        }
-        mServiceDisabled = disabled;
+    private void onResourcesAvailableLocked() {
         updateAllRecognitionsLocked();
     }
 
@@ -1039,7 +1030,6 @@
             if (mModule != null) {
                 mModule.detach();
                 mModule = null;
-                mServiceDisabled = false;
             }
         }
     }
@@ -1114,8 +1104,6 @@
             pw.print("  call active=");
             pw.println(mCallActive);
             pw.println("  SoundTrigger Power State=" + mSoundTriggerPowerSaveMode);
-            pw.print("  service disabled=");
-            pw.println(mServiceDisabled);
         }
     }
 
@@ -1329,8 +1317,7 @@
             mSoundTriggerPowerSaveMode = mPowerManager.getSoundTriggerPowerSaveMode();
         }
 
-        return !mCallActive && !mServiceDisabled
-                && isRecognitionAllowedByPowerState(
+        return !mCallActive && isRecognitionAllowedByPowerState(
                 modelData);
     }
 
@@ -1571,6 +1558,10 @@
             mModelState = MODEL_LOADED;
         }
 
+        synchronized void setNotLoaded() {
+            mModelState = MODEL_NOTLOADED;
+        }
+
         synchronized boolean isModelStarted() {
             return mModelState == MODEL_STARTED;
         }
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index a527e8d..466180b 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -2681,7 +2681,8 @@
      * @throws IllegalArgumentException if contentUri is empty
      */
     public void sendMultimediaMessage(@NonNull Context context, @NonNull Uri contentUri,
-            @Nullable String locationUrl, @Nullable Bundle configOverrides,
+            @Nullable String locationUrl,
+            @SuppressWarnings("NullableCollection") @Nullable Bundle configOverrides,
             @Nullable PendingIntent sentIntent, long messageId) {
         if (contentUri == null) {
             throw new IllegalArgumentException("Uri contentUri null");
@@ -2757,7 +2758,8 @@
      * @throws IllegalArgumentException if locationUrl or contentUri is empty
      */
     public void downloadMultimediaMessage(@NonNull Context context, @NonNull String locationUrl,
-            @NonNull Uri contentUri, @Nullable Bundle configOverrides,
+            @NonNull Uri contentUri,
+            @SuppressWarnings("NullableCollection") @Nullable Bundle configOverrides,
             @Nullable PendingIntent downloadedIntent, long messageId) {
         if (TextUtils.isEmpty(locationUrl)) {
             throw new IllegalArgumentException("Empty MMS location URL");
diff --git a/telephony/java/android/telephony/TelephonyDisplayInfo.java b/telephony/java/android/telephony/TelephonyDisplayInfo.java
index 88c66ac..f4e2ade 100644
--- a/telephony/java/android/telephony/TelephonyDisplayInfo.java
+++ b/telephony/java/android/telephony/TelephonyDisplayInfo.java
@@ -79,7 +79,7 @@
      *   <li>The device is connected to the NR cellular network on millimeter wave bands. </li>
      *   <li>The device is connected to the specific network which the carrier is using
      *   proprietary means to provide a faster overall data connection than would be otherwise
-     *   possible. This may include using other bands unique to the carrier, or carrier
+     *   possible.  This may include using other bands unique to the carrier, or carrier
      *   aggregation, for example.</li>
      * </ul>
      * One of the use case is that UX can show a different icon, for example, "5G+"
diff --git a/tests/SoundTriggerTestApp/res/layout/main.xml b/tests/SoundTriggerTestApp/res/layout/main.xml
index 2c6c8d7..1381c0a 100644
--- a/tests/SoundTriggerTestApp/res/layout/main.xml
+++ b/tests/SoundTriggerTestApp/res/layout/main.xml
@@ -73,6 +73,14 @@
             android:text="@string/play_trigger"
             android:onClick="onPlayTriggerButtonClicked"
             android:padding="20dp" />
+
+        <Button
+            android:id="@+id/get_state_id"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/get_model_state"
+            android:onClick="onGetModelStateButtonClicked"
+            android:padding="20dp" />
     </LinearLayout>
 
     <LinearLayout
diff --git a/tests/SoundTriggerTestApp/res/values/strings.xml b/tests/SoundTriggerTestApp/res/values/strings.xml
index c48b648..adb0fcf 100644
--- a/tests/SoundTriggerTestApp/res/values/strings.xml
+++ b/tests/SoundTriggerTestApp/res/values/strings.xml
@@ -22,6 +22,7 @@
     <string name="start_recog">Start</string>
     <string name="stop_recog">Stop</string>
     <string name="play_trigger">Play Trigger Audio</string>
+    <string name="get_model_state">Get State</string>
     <string name="capture">Capture Audio</string>
     <string name="stop_capture">Stop Capturing Audio</string>
     <string name="play_capture">Play Captured Audio</string>
diff --git a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerTestActivity.java b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerTestActivity.java
index c3c4cf5..72aa38d 100644
--- a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerTestActivity.java
+++ b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerTestActivity.java
@@ -257,6 +257,14 @@
         }
     }
 
+    public synchronized void onGetModelStateButtonClicked(View v) {
+        if (mService == null) {
+            Log.e(TAG, "Can't get model state: not bound to SoundTriggerTestService");
+        } else {
+            mService.getModelState(mSelectedModelUuid);
+        }
+    }
+
     public synchronized void onCaptureAudioCheckboxClicked(View v) {
         // See if we have the right permissions
         if (!mService.hasMicrophonePermission()) {
diff --git a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerTestService.java b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerTestService.java
index 380e299..6d4ffcf 100644
--- a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerTestService.java
+++ b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerTestService.java
@@ -23,6 +23,8 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
+import android.hardware.soundtrigger.SoundTrigger.RecognitionEvent;
+import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel;
 import android.media.AudioAttributes;
 import android.media.AudioFormat;
 import android.media.AudioManager;
@@ -46,6 +48,7 @@
 import java.util.Random;
 import java.util.UUID;
 
+
 public class SoundTriggerTestService extends Service {
     private static final String TAG = "SoundTriggerTestSrv";
     private static final String INTENT_ACTION = "com.android.intent.action.MANAGE_SOUND_TRIGGER";
@@ -57,6 +60,8 @@
     private Random mRandom;
     private UserActivity mUserActivity;
 
+    private static int captureCount;
+
     public interface UserActivity {
         void addModel(UUID modelUuid, String state);
         void setModelState(UUID modelUuid, String state);
@@ -131,6 +136,8 @@
                         } else if (command.equals("set_capture_timeout")) {
                             setCaptureAudioTimeout(getModelUuidFromIntent(intent),
                                     intent.getIntExtra("timeout", 5000));
+                        } else if (command.equals("get_model_state")) {
+                            getModelState(getModelUuidFromIntent(intent));
                         } else {
                             Log.e(TAG, "Unknown command '" + command + "'");
                         }
@@ -432,6 +439,17 @@
         return modelInfo != null && modelInfo.captureAudioTrack != null;
     }
 
+    public synchronized void getModelState(UUID modelUuid) {
+        ModelInfo modelInfo = mModelInfoMap.get(modelUuid);
+        if (modelInfo == null) {
+            postError("Could not find model for: " + modelUuid.toString());
+            return;
+        }
+        int status = mSoundTriggerUtil.getModelState(modelUuid);
+        postMessage("GetModelState for: " + modelInfo.name + " returns: "
+            + status);
+    }
+
     private void loadModelsInDataDir() {
         // Load all the models in the data dir.
         boolean loadedModel = false;
@@ -527,18 +545,29 @@
         }
     }
 
+
     private class CaptureAudioRecorder implements Runnable {
         private final ModelInfo mModelInfo;
+
+        // EventPayload and RecognitionEvent are equivalant.  Only one will be non-null.
         private final SoundTriggerDetector.EventPayload mEvent;
+        private final RecognitionEvent mRecognitionEvent;
 
         public CaptureAudioRecorder(ModelInfo modelInfo, SoundTriggerDetector.EventPayload event) {
             mModelInfo = modelInfo;
             mEvent = event;
+            mRecognitionEvent = null;
+        }
+
+        public CaptureAudioRecorder(ModelInfo modelInfo, RecognitionEvent event) {
+            mModelInfo = modelInfo;
+            mEvent = null;
+            mRecognitionEvent = event;
         }
 
         @Override
         public void run() {
-            AudioFormat format = mEvent.getCaptureAudioFormat();
+            AudioFormat format = getAudioFormat();
             if (format == null) {
                 postErrorToast("No audio format in recognition event.");
                 return;
@@ -600,18 +629,21 @@
                 }
 
                 audioRecord = new AudioRecord(attributes, format, bytesRequired,
-                        mEvent.getCaptureSession());
+                        getCaptureSession());
 
                 byte[] buffer = new byte[bytesRequired];
 
                 // Create a file so we can save the output data there for analysis later.
                 FileOutputStream fos  = null;
                 try {
-                    fos = new FileOutputStream( new File(
-                            getFilesDir() + File.separator
-                                    + mModelInfo.name.replace(' ', '_')
-                                    + "_capture_" + format.getChannelCount() + "ch_"
-                                    + format.getSampleRate() + "hz_" + encoding + ".pcm"));
+                    File file = new File(
+                        getFilesDir() + File.separator
+                        + mModelInfo.name.replace(' ', '_')
+                        + "_capture_" + format.getChannelCount() + "ch_"
+                        + format.getSampleRate() + "hz_" + encoding
+                        + "_" + (++captureCount) + ".pcm");
+                    Log.i(TAG, "Writing audio to: " + file);
+                    fos = new FileOutputStream(file);
                 } catch (IOException e) {
                     Log.e(TAG, "Failed to open output for saving PCM data", e);
                     postErrorToast("Failed to open output for saving PCM data: "
@@ -635,6 +667,10 @@
                     bytesRequired -= bytesRead;
                 }
                 audioRecord.stop();
+                if (fos != null) {
+                  fos.flush();
+                  fos.close();
+                }
             } catch (Exception e) {
                 Log.e(TAG, "Error recording trigger audio", e);
                 postErrorToast("Error recording trigger audio: " + e.getMessage());
@@ -651,6 +687,26 @@
                 setModelState(mModelInfo, "Recording finished");
             }
         }
+
+        private AudioFormat getAudioFormat() {
+            if (mEvent != null) {
+                return mEvent.getCaptureAudioFormat();
+            }
+            if (mRecognitionEvent != null) {
+                return mRecognitionEvent.captureFormat;
+            }
+            return null;
+        }
+
+        private int getCaptureSession() {
+            if (mEvent != null) {
+                return mEvent.getCaptureSession();
+            }
+            if (mRecognitionEvent != null) {
+                return mRecognitionEvent.captureSession;
+            }
+            return 0;
+        }
     }
 
     // Implementation of SoundTriggerDetector.Callback.
diff --git a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerUtil.java b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerUtil.java
index cfe8c85..996a78f 100644
--- a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerUtil.java
+++ b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerUtil.java
@@ -18,6 +18,8 @@
 
 import android.annotation.Nullable;
 import android.content.Context;
+import android.hardware.soundtrigger.SoundTrigger.RecognitionEvent;
+import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel;
 import android.media.soundtrigger.SoundTriggerDetector;
 import android.media.soundtrigger.SoundTriggerManager;
 import android.os.RemoteException;
@@ -27,6 +29,7 @@
 
 import com.android.internal.app.ISoundTriggerService;
 
+import java.lang.reflect.Method;
 import java.lang.RuntimeException;
 import java.util.UUID;
 
@@ -50,13 +53,31 @@
      * The sound model must contain a valid UUID.
      *
      * @param soundModel The sound model to add/update.
+     * @return The true if the model was loaded successfully, false otherwise.
      */
     public boolean addOrUpdateSoundModel(SoundTriggerManager.Model soundModel) {
         if (soundModel == null) {
             throw new RuntimeException("Bad sound model");
         }
         mSoundTriggerManager.updateModel(soundModel);
-        return true;
+        // TODO: call loadSoundModel in the soundtrigger manager updateModel method
+        // instead of here. It is needed to keep soundtrigger manager internal
+        // state consistent.
+        return mSoundTriggerManager
+                .loadSoundModel(getGenericSoundModel(soundModel)) == 0;
+    }
+
+    private GenericSoundModel getGenericSoundModel(
+        SoundTriggerManager.Model soundModel) {
+        try {
+            Method method = SoundTriggerManager.Model.class
+                            .getDeclaredMethod("getGenericSoundModel");
+            method.setAccessible(true);
+            return (GenericSoundModel) method.invoke(soundModel);
+        } catch (ReflectiveOperationException e) {
+            Log.e(TAG, "Failed to getGenericSoundModel: " + soundModel, e);
+            return null;
+        }
     }
 
     /**
@@ -92,6 +113,16 @@
         return true;
     }
 
+    /**
+     * Get the current model state
+     *
+     * @param modelId The model ID to look-up the sound model for.
+     * @return 0 if the call succeeds, or an error code if it fails.
+     */
+    public int getModelState(UUID modelId) {
+        return mSoundTriggerManager.getModelState(modelId);
+    }
+
     public SoundTriggerDetector createSoundTriggerDetector(UUID modelId,
             SoundTriggerDetector.Callback callback) {
         return mSoundTriggerManager.createSoundTriggerDetector(modelId, callback, null);