Merge "[1/N] APIs of verification service, session and status" into main
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 1a1f354..4b6c62e 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -91,6 +91,7 @@
     field public static final String BIND_TRANSLATION_SERVICE = "android.permission.BIND_TRANSLATION_SERVICE";
     field public static final String BIND_TRUST_AGENT = "android.permission.BIND_TRUST_AGENT";
     field public static final String BIND_TV_REMOTE_SERVICE = "android.permission.BIND_TV_REMOTE_SERVICE";
+    field @FlaggedApi("android.content.pm.verification_service") public static final String BIND_VERIFICATION_AGENT = "android.permission.BIND_VERIFICATION_AGENT";
     field public static final String BIND_VISUAL_QUERY_DETECTION_SERVICE = "android.permission.BIND_VISUAL_QUERY_DETECTION_SERVICE";
     field public static final String BIND_WALLPAPER_EFFECTS_GENERATION_SERVICE = "android.permission.BIND_WALLPAPER_EFFECTS_GENERATION_SERVICE";
     field public static final String BIND_WEARABLE_SENSING_SERVICE = "android.permission.BIND_WEARABLE_SENSING_SERVICE";
@@ -412,6 +413,7 @@
     field @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public static final String USE_ON_DEVICE_INTELLIGENCE = "android.permission.USE_ON_DEVICE_INTELLIGENCE";
     field public static final String USE_RESERVED_DISK = "android.permission.USE_RESERVED_DISK";
     field public static final String UWB_PRIVILEGED = "android.permission.UWB_PRIVILEGED";
+    field @FlaggedApi("android.content.pm.verification_service") public static final String VERIFICATION_AGENT = "android.permission.VERIFICATION_AGENT";
     field @FlaggedApi("android.os.vibrator.vendor_vibration_effects") public static final String VIBRATE_VENDOR_EFFECTS = "android.permission.VIBRATE_VENDOR_EFFECTS";
     field public static final String WHITELIST_AUTO_REVOKE_PERMISSIONS = "android.permission.WHITELIST_AUTO_REVOKE_PERMISSIONS";
     field public static final String WHITELIST_RESTRICTED_PERMISSIONS = "android.permission.WHITELIST_RESTRICTED_PERMISSIONS";
@@ -4303,6 +4305,7 @@
     method @Deprecated @RequiresPermission(android.Manifest.permission.INTENT_FILTER_VERIFICATION_AGENT) public abstract void verifyIntentFilter(int, int, @NonNull java.util.List<java.lang.String>);
     field public static final String ACTION_REQUEST_PERMISSIONS = "android.content.pm.action.REQUEST_PERMISSIONS";
     field public static final String ACTION_REQUEST_PERMISSIONS_FOR_OTHER = "android.content.pm.action.REQUEST_PERMISSIONS_FOR_OTHER";
+    field @FlaggedApi("android.content.pm.verification_service") public static final String ACTION_VERIFY_PACKAGE = "android.content.pm.action.VERIFY_PACKAGE";
     field @FlaggedApi("android.content.pm.asl_in_apk_app_metadata_source") public static final int APP_METADATA_SOURCE_APK = 1; // 0x1
     field @FlaggedApi("android.content.pm.asl_in_apk_app_metadata_source") public static final int APP_METADATA_SOURCE_INSTALLER = 2; // 0x2
     field @FlaggedApi("android.content.pm.asl_in_apk_app_metadata_source") public static final int APP_METADATA_SOURCE_SYSTEM_IMAGE = 3; // 0x3
@@ -4616,6 +4619,61 @@
 
 }
 
+package android.content.pm.verify.pkg {
+
+  @FlaggedApi("android.content.pm.verification_service") public final class VerificationSession implements android.os.Parcelable {
+    method public int describeContents();
+    method @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT) public long extendTimeRemaining(long);
+    method @NonNull public java.util.List<android.content.pm.SharedLibraryInfo> getDeclaredLibraries();
+    method @NonNull public android.os.PersistableBundle getExtensionParams();
+    method public int getId();
+    method public int getInstallSessionId();
+    method @NonNull public String getPackageName();
+    method @NonNull public android.content.pm.SigningInfo getSigningInfo();
+    method @NonNull public android.net.Uri getStagedPackageUri();
+    method @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT) public long getTimeoutTime();
+    method @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT) public void reportVerificationComplete(@NonNull android.content.pm.verify.pkg.VerificationStatus);
+    method @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT) public void reportVerificationComplete(@NonNull android.content.pm.verify.pkg.VerificationStatus, @NonNull android.os.PersistableBundle);
+    method @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT) public void reportVerificationIncomplete(int);
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.verify.pkg.VerificationSession> CREATOR;
+    field public static final int VERIFICATION_INCOMPLETE_NETWORK_LIMITED = 2; // 0x2
+    field public static final int VERIFICATION_INCOMPLETE_NETWORK_UNAVAILABLE = 1; // 0x1
+    field public static final int VERIFICATION_INCOMPLETE_UNKNOWN = 0; // 0x0
+  }
+
+  @FlaggedApi("android.content.pm.verification_service") public final class VerificationStatus implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getAslStatus();
+    method @NonNull public String getFailureMessage();
+    method public boolean isVerified();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.verify.pkg.VerificationStatus> CREATOR;
+    field public static final int VERIFIER_STATUS_ASL_BAD = 2; // 0x2
+    field public static final int VERIFIER_STATUS_ASL_GOOD = 1; // 0x1
+    field public static final int VERIFIER_STATUS_ASL_UNDEFINED = 0; // 0x0
+  }
+
+  public static final class VerificationStatus.Builder {
+    ctor public VerificationStatus.Builder();
+    method @NonNull public android.content.pm.verify.pkg.VerificationStatus build();
+    method @NonNull public android.content.pm.verify.pkg.VerificationStatus.Builder setAslStatus(int);
+    method @NonNull public android.content.pm.verify.pkg.VerificationStatus.Builder setFailureMessage(@NonNull String);
+    method @NonNull public android.content.pm.verify.pkg.VerificationStatus.Builder setVerified(boolean);
+  }
+
+  @FlaggedApi("android.content.pm.verification_service") public abstract class VerifierService extends android.app.Service {
+    ctor public VerifierService();
+    method @Nullable public android.os.IBinder onBind(@Nullable android.content.Intent);
+    method public abstract void onPackageNameAvailable(@NonNull String);
+    method public abstract void onVerificationCancelled(@NonNull String);
+    method public abstract void onVerificationRequired(@NonNull android.content.pm.verify.pkg.VerificationSession);
+    method public abstract void onVerificationRetry(@NonNull android.content.pm.verify.pkg.VerificationSession);
+    method public abstract void onVerificationTimeout(int);
+  }
+
+}
+
 package android.content.rollback {
 
   public final class PackageRollbackInfo implements android.os.Parcelable {
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index fb2655c..e985f88 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -5039,6 +5039,25 @@
             "android.content.pm.action.REQUEST_PERMISSIONS_FOR_OTHER";
 
     /**
+     * Used by the system to query a {@link android.content.pm.verify.pkg.VerifierService} provider,
+     * which registers itself via an intent-filter handling this action.
+     *
+     * <p class="note">Only the system can bind to such a verifier service. This is protected by the
+     * {@link android.Manifest.permission#BIND_VERIFICATION_AGENT} permission. The verifier service
+     * app should protect the service by adding this permission in the service declaration in its
+     * manifest.
+     * <p>
+     * A verifier service must be a privileged app and hold the
+     * {@link android.Manifest.permission#VERIFICATION_AGENT} permission.
+     *
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(android.content.pm.Flags.FLAG_VERIFICATION_SERVICE)
+    @SdkConstant(SdkConstantType.SERVICE_ACTION)
+    public static final String ACTION_VERIFY_PACKAGE = "android.content.pm.action.VERIFY_PACKAGE";
+
+    /**
      * The names of the requested permissions.
      * <p>
      * <strong>Type:</strong> String[]
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index 160cbdf..300740e 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -309,3 +309,11 @@
     description: "Feature flag to enable the holder of SYSTEM_APP_PROTECTION_SERVICE role to silently delete packages. To be deprecated by delete_packages_silently."
     bug: "361776825"
 }
+
+flag {
+    name: "verification_service"
+    namespace: "package_manager_service"
+    description: "Feature flag to enable the new verification service."
+    bug: "360129103"
+    is_fixed_read_only: true
+}
diff --git a/core/java/android/content/pm/verify/pkg/IVerificationSessionCallback.aidl b/core/java/android/content/pm/verify/pkg/IVerificationSessionCallback.aidl
new file mode 100644
index 0000000..38a7956
--- /dev/null
+++ b/core/java/android/content/pm/verify/pkg/IVerificationSessionCallback.aidl
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 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.verify.pkg;
+
+import android.content.pm.verify.pkg.VerificationStatus;
+import android.os.PersistableBundle;
+
+/**
+ * Oneway interface that allows the verifier to send response or verification results back to
+ * the system.
+ * @hide
+ */
+oneway interface IVerificationSessionCallback {
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)")
+    void reportVerificationIncomplete(int verificationId, int reason);
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)")
+    void reportVerificationComplete(int verificationId, in VerificationStatus status);
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)")
+    void reportVerificationCompleteWithExtensionResponse(int verificationId, in VerificationStatus status, in PersistableBundle response);
+}
diff --git a/core/java/android/content/pm/verify/pkg/IVerificationSessionInterface.aidl b/core/java/android/content/pm/verify/pkg/IVerificationSessionInterface.aidl
new file mode 100644
index 0000000..7a9484a
--- /dev/null
+++ b/core/java/android/content/pm/verify/pkg/IVerificationSessionInterface.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2024 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.verify.pkg;
+
+/**
+ * Non-oneway interface that allows the verifier to retrieve information from the system.
+ * @hide
+ */
+interface IVerificationSessionInterface {
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)")
+    long getTimeoutTime(int verificationId);
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)")
+    long extendTimeRemaining(int verificationId, long additionalMs);
+}
\ No newline at end of file
diff --git a/core/java/android/content/pm/verify/pkg/IVerifierService.aidl b/core/java/android/content/pm/verify/pkg/IVerifierService.aidl
new file mode 100644
index 0000000..d3071fd
--- /dev/null
+++ b/core/java/android/content/pm/verify/pkg/IVerifierService.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 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.verify.pkg;
+
+import android.content.pm.verify.pkg.VerificationSession;
+
+/**
+ * Oneway interface that allows the system to communicate to the verifier service agent.
+ * @hide
+ */
+oneway interface IVerifierService {
+    void onPackageNameAvailable(in String packageName);
+    void onVerificationCancelled(in String packageName);
+    void onVerificationRequired(in VerificationSession session);
+    void onVerificationRetry(in VerificationSession session);
+    void onVerificationTimeout(int verificationId);
+}
\ No newline at end of file
diff --git a/core/java/android/content/pm/verify/pkg/VerificationSession.aidl b/core/java/android/content/pm/verify/pkg/VerificationSession.aidl
new file mode 100644
index 0000000..ac85585
--- /dev/null
+++ b/core/java/android/content/pm/verify/pkg/VerificationSession.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2024 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.verify.pkg;
+
+/** @hide */
+parcelable VerificationSession;
diff --git a/core/java/android/content/pm/verify/pkg/VerificationSession.java b/core/java/android/content/pm/verify/pkg/VerificationSession.java
new file mode 100644
index 0000000..70b4a02
--- /dev/null
+++ b/core/java/android/content/pm/verify/pkg/VerificationSession.java
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 2024 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.verify.pkg;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.content.pm.Flags;
+import android.content.pm.SharedLibraryInfo;
+import android.content.pm.SigningInfo;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.PersistableBundle;
+import android.os.RemoteException;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * This class is used by the system to describe the details about a verification request sent to the
+ * verification agent, aka the verifier. It includes the interfaces for the verifier to communicate
+ * back to the system.
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_VERIFICATION_SERVICE)
+@SystemApi
+public final class VerificationSession implements Parcelable {
+    /**
+     * The verification cannot be completed because of unknown reasons.
+     */
+    public static final int VERIFICATION_INCOMPLETE_UNKNOWN = 0;
+    /**
+     * The verification cannot be completed because the network is unavailable.
+     */
+    public static final int VERIFICATION_INCOMPLETE_NETWORK_UNAVAILABLE = 1;
+    /**
+     * The verification cannot be completed because the network is limited.
+     */
+    public static final int VERIFICATION_INCOMPLETE_NETWORK_LIMITED = 2;
+
+    /**
+     * @hide
+     */
+    @IntDef(prefix = {"VERIFICATION_INCOMPLETE_"}, value = {
+            VERIFICATION_INCOMPLETE_NETWORK_UNAVAILABLE,
+            VERIFICATION_INCOMPLETE_NETWORK_LIMITED,
+            VERIFICATION_INCOMPLETE_UNKNOWN,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface VerificationIncompleteReason {
+    }
+
+    private final int mId;
+    private final int mInstallSessionId;
+    @NonNull
+    private final String mPackageName;
+    @NonNull
+    private final Uri mStagedPackageUri;
+    @NonNull
+    private final SigningInfo mSigningInfo;
+    @NonNull
+    private final List<SharedLibraryInfo> mDeclaredLibraries;
+    @NonNull
+    private final PersistableBundle mExtensionParams;
+    @NonNull
+    private final IVerificationSessionInterface mSession;
+    @NonNull
+    private final IVerificationSessionCallback mCallback;
+
+    /**
+     * Constructor used by the system to describe the details of a verification session.
+     * @hide
+     */
+    public VerificationSession(int id, int installSessionId, @NonNull String packageName,
+            @NonNull Uri stagedPackageUri, @NonNull SigningInfo signingInfo,
+            @NonNull List<SharedLibraryInfo> declaredLibraries,
+            @NonNull PersistableBundle extensionParams,
+            @NonNull IVerificationSessionInterface session,
+            @NonNull IVerificationSessionCallback callback) {
+        mId = id;
+        mInstallSessionId = installSessionId;
+        mPackageName = packageName;
+        mStagedPackageUri = stagedPackageUri;
+        mSigningInfo = signingInfo;
+        mDeclaredLibraries = declaredLibraries;
+        mExtensionParams = extensionParams;
+        mSession = session;
+        mCallback = callback;
+    }
+
+    /**
+     * A unique identifier tied to this specific verification session.
+     */
+    public int getId() {
+        return mId;
+    }
+
+    /**
+     * The package name of the app that is to be verified.
+     */
+    public @NonNull String getPackageName() {
+        return mPackageName;
+    }
+
+    /**
+     * The id of the installation session associated with the verification.
+     */
+    public int getInstallSessionId() {
+        return mInstallSessionId;
+    }
+
+    /**
+     * The Uri of the path where the package's code files are located.
+     */
+    public @NonNull Uri getStagedPackageUri() {
+        return mStagedPackageUri;
+    }
+
+    /**
+     * Signing info of the package to be verified.
+     */
+    public @NonNull SigningInfo getSigningInfo() {
+        return mSigningInfo;
+    }
+
+    /**
+     * Returns a mapping of any shared libraries declared in the manifest
+     * to the {@link SharedLibraryInfo#Type} that is declared. This will be an empty
+     * map if no shared libraries are declared by the package.
+     */
+    @NonNull
+    public List<SharedLibraryInfo> getDeclaredLibraries() {
+        return Collections.unmodifiableList(mDeclaredLibraries);
+    }
+
+    /**
+     * Returns any extension params associated with the verification request.
+     */
+    @NonNull
+    public PersistableBundle getExtensionParams() {
+        return mExtensionParams;
+    }
+
+    /**
+     * Get the value of Clock.elapsedRealtime() at which time this verification
+     * will timeout as incomplete if no other verification response is provided.
+     */
+    @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)
+    public long getTimeoutTime() {
+        try {
+            return mSession.getTimeoutTime(mId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Extend the timeout for this session by the provided additionalMs to
+     * fetch relevant information over the network or wait for the network.
+     * This may be called multiple times. If the request would bypass any max
+     * duration by the system, the method will return a lower value than the
+     * requested amount that indicates how much the time was extended.
+     */
+    @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)
+    public long extendTimeRemaining(long additionalMs) {
+        try {
+            return mSession.extendTimeRemaining(mId, additionalMs);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Report to the system that verification could not be completed along
+     * with an approximate reason to pass on to the installer.
+     */
+    @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)
+    public void reportVerificationIncomplete(@VerificationIncompleteReason int reason) {
+        try {
+            mCallback.reportVerificationIncomplete(mId, reason);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Report to the system that the verification has completed and the
+     * install process may act on that status to either block in the case
+     * of failure or continue to process the install in the case of success.
+     */
+    @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)
+    public void reportVerificationComplete(@NonNull VerificationStatus status) {
+        try {
+            mCallback.reportVerificationComplete(mId, status);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Same as {@link #reportVerificationComplete(VerificationStatus)}, but also provide
+     * a result to the extension params provided in the request, which will be passed to the
+     * installer in the installation result.
+     */
+    @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)
+    public void reportVerificationComplete(@NonNull VerificationStatus status,
+            @NonNull PersistableBundle response) {
+        try {
+            mCallback.reportVerificationCompleteWithExtensionResponse(mId, status, response);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    private VerificationSession(@NonNull Parcel in) {
+        mId = in.readInt();
+        mInstallSessionId = in.readInt();
+        mPackageName = in.readString8();
+        mStagedPackageUri = Uri.CREATOR.createFromParcel(in);
+        mSigningInfo = SigningInfo.CREATOR.createFromParcel(in);
+        mDeclaredLibraries = in.createTypedArrayList(SharedLibraryInfo.CREATOR);
+        mExtensionParams = in.readPersistableBundle(getClass().getClassLoader());
+        mSession = IVerificationSessionInterface.Stub.asInterface(in.readStrongBinder());
+        mCallback = IVerificationSessionCallback.Stub.asInterface(in.readStrongBinder());
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mId);
+        dest.writeInt(mInstallSessionId);
+        dest.writeString8(mPackageName);
+        Uri.writeToParcel(dest, mStagedPackageUri);
+        mSigningInfo.writeToParcel(dest, flags);
+        dest.writeTypedList(mDeclaredLibraries);
+        dest.writePersistableBundle(mExtensionParams);
+        dest.writeStrongBinder(mSession.asBinder());
+        dest.writeStrongBinder(mCallback.asBinder());
+    }
+
+    @NonNull
+    public static final Creator<VerificationSession> CREATOR = new Creator<>() {
+        @Override
+        public VerificationSession createFromParcel(@NonNull Parcel in) {
+            return new VerificationSession(in);
+        }
+
+        @Override
+        public VerificationSession[] newArray(int size) {
+            return new VerificationSession[size];
+        }
+    };
+}
diff --git a/core/java/android/content/pm/verify/pkg/VerificationStatus.aidl b/core/java/android/content/pm/verify/pkg/VerificationStatus.aidl
new file mode 100644
index 0000000..6a1cb4f
--- /dev/null
+++ b/core/java/android/content/pm/verify/pkg/VerificationStatus.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2024 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.verify.pkg;
+
+/** @hide */
+parcelable VerificationStatus;
diff --git a/core/java/android/content/pm/verify/pkg/VerificationStatus.java b/core/java/android/content/pm/verify/pkg/VerificationStatus.java
new file mode 100644
index 0000000..4d0379d7
--- /dev/null
+++ b/core/java/android/content/pm/verify/pkg/VerificationStatus.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2024 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.verify.pkg;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.content.pm.Flags;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * This class is used by the verifier to describe the status of the verification request, whether
+ * it's successful or it has failed along with any relevant details.
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_VERIFICATION_SERVICE)
+public final class  VerificationStatus implements Parcelable {
+    /**
+     * The ASL status has not been determined.  This happens in situations where the verification
+     * service is not monitoring ASLs, and means the ASL data in the app is not necessarily bad but
+     * can't be trusted.
+     */
+    public static final int VERIFIER_STATUS_ASL_UNDEFINED = 0;
+
+    /**
+     * The app's ASL data is considered to be in a good state.
+     */
+    public static final int VERIFIER_STATUS_ASL_GOOD = 1;
+
+    /**
+     * There is something bad in the app's ASL data; the user should be warned about this when shown
+     * the ASL data and/or appropriate decisions made about the use of this data by the platform.
+     */
+    public static final int VERIFIER_STATUS_ASL_BAD = 2;
+
+    /** @hide */
+    @IntDef(prefix = {"VERIFIER_STATUS_ASL_"}, value = {
+            VERIFIER_STATUS_ASL_UNDEFINED,
+            VERIFIER_STATUS_ASL_GOOD,
+            VERIFIER_STATUS_ASL_BAD,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface VerifierStatusAsl {}
+
+    private boolean mIsVerified;
+    private @VerifierStatusAsl int mAslStatus;
+    @NonNull
+    private String mFailuresMessage = "";
+
+    private VerificationStatus() {}
+
+    /**
+     * @return whether the status is set to verified or not.
+     */
+    public boolean isVerified() {
+        return mIsVerified;
+    }
+
+    /**
+     * @return the failure message associated with the failure status.
+     */
+    @NonNull
+    public String getFailureMessage() {
+        return mFailuresMessage;
+    }
+
+    /**
+     * @return the asl status.
+     */
+    public @VerifierStatusAsl int getAslStatus() {
+        return mAslStatus;
+    }
+
+    /**
+     * Builder to construct a {@link VerificationStatus} object.
+     */
+    public static final class Builder {
+        final VerificationStatus mStatus = new VerificationStatus();
+
+        /**
+         * Set in the status whether the verification has succeeded or failed.
+         */
+        @NonNull
+        public Builder setVerified(boolean verified) {
+            mStatus.mIsVerified = verified;
+            return this;
+        }
+
+        /**
+         * Set a developer-facing failure message to include in the verification failure status.
+         */
+        @NonNull
+        public Builder setFailureMessage(@NonNull String failureMessage) {
+            mStatus.mFailuresMessage = failureMessage;
+            return this;
+        }
+
+        /**
+         * Set the ASL status, as defined in {@link VerifierStatusAsl}.
+         */
+        @NonNull
+        public Builder setAslStatus(@VerifierStatusAsl int aslStatus) {
+            mStatus.mAslStatus = aslStatus;
+            return this;
+        }
+
+        /**
+         * Build the status object.
+         */
+        @NonNull
+        public VerificationStatus build() {
+            return mStatus;
+        }
+    }
+
+    private VerificationStatus(Parcel in) {
+        mIsVerified = in.readBoolean();
+        mAslStatus = in.readInt();
+        mFailuresMessage = in.readString8();
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeBoolean(mIsVerified);
+        dest.writeInt(mAslStatus);
+        dest.writeString8(mFailuresMessage);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @NonNull
+    public static final Creator<VerificationStatus> CREATOR = new Creator<>() {
+        @Override
+        public VerificationStatus createFromParcel(@NonNull Parcel in) {
+            return new VerificationStatus(in);
+        }
+
+        @Override
+        public VerificationStatus[] newArray(int size) {
+            return new VerificationStatus[size];
+        }
+    };
+}
diff --git a/core/java/android/content/pm/verify/pkg/VerifierService.java b/core/java/android/content/pm/verify/pkg/VerifierService.java
new file mode 100644
index 0000000..ccf2119
--- /dev/null
+++ b/core/java/android/content/pm/verify/pkg/VerifierService.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2024 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.verify.pkg;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.Service;
+import android.content.Intent;
+import android.content.pm.Flags;
+import android.content.pm.PackageManager;
+import android.os.IBinder;
+
+/**
+ * A base service implementation for the verifier agent to implement.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_VERIFICATION_SERVICE)
+public abstract class VerifierService extends Service {
+    /**
+     * Called when a package name is available for a pending verification,
+     * giving the verifier opportunity to pre-fetch any relevant information
+     * that may be needed should a verification for the package be required.
+     */
+    public abstract void onPackageNameAvailable(@NonNull String packageName);
+
+    /**
+     * Called when a package recently provided via {@link #onPackageNameAvailable}
+     * is no longer expected to be installed. This is a hint that any pre-fetch or
+     * cache created as a result of the previous call may be be cleared.
+     * <p>This method will never be called after {@link #onVerificationRequired} is called for the
+     * same package. Once a verification is officially requested by
+     * {@link #onVerificationRequired}, it cannot be cancelled.
+     * </p>
+     */
+    public abstract void onVerificationCancelled(@NonNull String packageName);
+
+    /**
+     * Called when an application needs to be verified. Details about the
+     * verification and actions that can be taken on it will be encapsulated in
+     * the provided {@link VerificationSession} parameter.
+     */
+    public abstract void onVerificationRequired(@NonNull VerificationSession session);
+
+    /**
+     * Called when a verification needs to be retried. This can be encountered
+     * when a prior verification was marked incomplete and the user has indicated
+     * that they've resolved the issue, or when a timeout is reached, but the
+     * the system is attempting to retry. Details about the
+     * verification and actions that can be taken on it will be encapsulated in
+     * the provided {@link VerificationSession} parameter.
+     */
+    public abstract void onVerificationRetry(@NonNull VerificationSession session);
+
+    /**
+     * Called in the case that an active verification has failed. Any APIs called
+     * on the {@link VerificationSession} instance associated with this {@code verificationId} will
+     * throw an {@link IllegalStateException}.
+     */
+    public abstract void onVerificationTimeout(int verificationId);
+
+    /**
+     * Called when the verifier service is bound to the system.
+     */
+    public @Nullable IBinder onBind(@Nullable Intent intent) {
+        if (intent == null || !PackageManager.ACTION_VERIFY_PACKAGE.equals(intent.getAction())) {
+            return null;
+        }
+        return new IVerifierService.Stub() {
+            @Override
+            public void onPackageNameAvailable(@NonNull String packageName) {
+                VerifierService.this.onPackageNameAvailable(packageName);
+            }
+
+            @Override
+            public void onVerificationCancelled(@NonNull String packageName) {
+                VerifierService.this.onVerificationCancelled(packageName);
+            }
+
+            @Override
+            public void onVerificationRequired(@NonNull VerificationSession session) {
+                VerifierService.this.onVerificationRequired(session);
+            }
+
+            @Override
+            public void onVerificationRetry(@NonNull VerificationSession session) {
+                VerifierService.this.onVerificationRetry(session);
+            }
+
+            @Override
+            public void onVerificationTimeout(int verificationId) {
+                VerifierService.this.onVerificationTimeout(verificationId);
+            }
+        };
+    }
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index d35c66e..ed33ede 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -8449,6 +8449,29 @@
     <permission android:name="android.permission.RESERVED_FOR_TESTING_SIGNATURE"
                 android:protectionLevel="signature"/>
 
+    <!-- @SystemApi
+        @FlaggedApi("android.content.pm.verification_service")
+        Allows app to be the verification agent to verify packages.
+        <p>Protection level: signature|privileged
+        @hide
+    -->
+    <permission android:name="android.permission.VERIFICATION_AGENT"
+        android:protectionLevel="signature|privileged"
+        android:featureFlag="android.content.pm.verification_service" />
+
+    <!-- @SystemApi
+        @FlaggedApi("android.content.pm.verification_service")
+        Must be required by a privileged {@link android.content.pm.verify.pkg.VerifierService}
+        to ensure that only the system can bind to it.
+        This permission should not be held by anything other than the system.
+        <p>Not for use by third-party applications. </p>
+        <p>Protection level: signature
+        @hide
+    -->
+    <permission android:name="android.permission.BIND_VERIFICATION_AGENT"
+        android:protectionLevel="internal"
+        android:featureFlag="android.content.pm.verification_service" />
+
     <!-- Attribution for Geofencing service. -->
     <attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/>
     <!-- Attribution for Country Detector. -->
diff --git a/core/tests/coretests/src/android/content/pm/verify/VerificationSessionTest.java b/core/tests/coretests/src/android/content/pm/verify/VerificationSessionTest.java
new file mode 100644
index 0000000..987f68d
--- /dev/null
+++ b/core/tests/coretests/src/android/content/pm/verify/VerificationSessionTest.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2024 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.verify;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.pm.SharedLibraryInfo;
+import android.content.pm.SigningInfo;
+import android.content.pm.VersionedPackage;
+import android.content.pm.verify.pkg.IVerificationSessionCallback;
+import android.content.pm.verify.pkg.IVerificationSessionInterface;
+import android.content.pm.verify.pkg.VerificationSession;
+import android.content.pm.verify.pkg.VerificationStatus;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.PersistableBundle;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class VerificationSessionTest {
+    private static final int TEST_ID = 100;
+    private static final int TEST_INSTALL_SESSION_ID = 33;
+    private static final String TEST_PACKAGE_NAME = "com.foo";
+    private static final Uri TEST_PACKAGE_URI = Uri.parse("test://test");
+    private static final SigningInfo TEST_SIGNING_INFO = new SigningInfo();
+    private static final SharedLibraryInfo TEST_SHARED_LIBRARY_INFO1 =
+            new SharedLibraryInfo("sharedLibPath1", TEST_PACKAGE_NAME,
+                    Collections.singletonList("path1"), "sharedLib1", 101,
+                    SharedLibraryInfo.TYPE_DYNAMIC, new VersionedPackage(TEST_PACKAGE_NAME, 1),
+                    null, null, false);
+    private static final SharedLibraryInfo TEST_SHARED_LIBRARY_INFO2 =
+            new SharedLibraryInfo("sharedLibPath2", TEST_PACKAGE_NAME,
+                    Collections.singletonList("path2"), "sharedLib2", 102,
+                    SharedLibraryInfo.TYPE_DYNAMIC,
+                    new VersionedPackage(TEST_PACKAGE_NAME, 2), null, null, false);
+    private static final long TEST_TIMEOUT_TIME = System.currentTimeMillis();
+    private static final long TEST_EXTEND_TIME = 2000L;
+    private static final String TEST_KEY = "test key";
+    private static final String TEST_VALUE = "test value";
+
+    private final ArrayList<SharedLibraryInfo> mTestDeclaredLibraries = new ArrayList<>();
+    private final PersistableBundle mTestExtensionParams = new PersistableBundle();
+    @Mock
+    private IVerificationSessionInterface mTestSessionInterface;
+    @Mock
+    private IVerificationSessionCallback mTestCallback;
+    private VerificationSession mTestSession;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mTestDeclaredLibraries.add(TEST_SHARED_LIBRARY_INFO1);
+        mTestDeclaredLibraries.add(TEST_SHARED_LIBRARY_INFO2);
+        mTestExtensionParams.putString(TEST_KEY, TEST_VALUE);
+        mTestSession = new VerificationSession(TEST_ID, TEST_INSTALL_SESSION_ID,
+                TEST_PACKAGE_NAME, TEST_PACKAGE_URI, TEST_SIGNING_INFO, mTestDeclaredLibraries,
+                mTestExtensionParams, mTestSessionInterface, mTestCallback);
+    }
+
+    @Test
+    public void testParcel() {
+        Parcel parcel = Parcel.obtain();
+        mTestSession.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        VerificationSession sessionFromParcel =
+                VerificationSession.CREATOR.createFromParcel(parcel);
+        assertThat(sessionFromParcel.getId()).isEqualTo(TEST_ID);
+        assertThat(sessionFromParcel.getInstallSessionId()).isEqualTo(TEST_INSTALL_SESSION_ID);
+        assertThat(sessionFromParcel.getPackageName()).isEqualTo(TEST_PACKAGE_NAME);
+        assertThat(sessionFromParcel.getStagedPackageUri()).isEqualTo(TEST_PACKAGE_URI);
+        assertThat(sessionFromParcel.getSigningInfo().getSigningDetails())
+                .isEqualTo(TEST_SIGNING_INFO.getSigningDetails());
+        List<SharedLibraryInfo> declaredLibrariesFromParcel =
+                sessionFromParcel.getDeclaredLibraries();
+        assertThat(declaredLibrariesFromParcel).hasSize(2);
+        // SharedLibraryInfo doesn't have a "equals" method, so we have to check it indirectly
+        assertThat(declaredLibrariesFromParcel.getFirst().toString())
+                .isEqualTo(TEST_SHARED_LIBRARY_INFO1.toString());
+        assertThat(declaredLibrariesFromParcel.get(1).toString())
+                .isEqualTo(TEST_SHARED_LIBRARY_INFO2.toString());
+        // We can't directly test with PersistableBundle.equals() because the parceled bundle's
+        // structure is different, but all the key/value pairs should be preserved as before.
+        assertThat(sessionFromParcel.getExtensionParams().getString(TEST_KEY))
+                .isEqualTo(mTestExtensionParams.getString(TEST_KEY));
+    }
+
+    @Test
+    public void testInterface() throws Exception {
+        when(mTestSessionInterface.getTimeoutTime(anyInt())).thenAnswer(i -> TEST_TIMEOUT_TIME);
+        when(mTestSessionInterface.extendTimeRemaining(anyInt(), anyLong())).thenAnswer(
+                i -> i.getArguments()[1]);
+
+        assertThat(mTestSession.getTimeoutTime()).isEqualTo(TEST_TIMEOUT_TIME);
+        verify(mTestSessionInterface, times(1)).getTimeoutTime(eq(TEST_ID));
+        assertThat(mTestSession.extendTimeRemaining(TEST_EXTEND_TIME)).isEqualTo(TEST_EXTEND_TIME);
+        verify(mTestSessionInterface, times(1)).extendTimeRemaining(
+                eq(TEST_ID), eq(TEST_EXTEND_TIME));
+    }
+
+    @Test
+    public void testCallback() throws Exception {
+        PersistableBundle response = new PersistableBundle();
+        response.putString("test key", "test value");
+        final VerificationStatus status =
+                new VerificationStatus.Builder().setVerified(true).build();
+        mTestSession.reportVerificationComplete(status);
+        verify(mTestCallback, times(1)).reportVerificationComplete(
+                eq(TEST_ID), eq(status));
+        mTestSession.reportVerificationComplete(status, response);
+        verify(mTestCallback, times(1))
+                .reportVerificationCompleteWithExtensionResponse(
+                        eq(TEST_ID), eq(status), eq(response));
+
+        final int reason = VerificationSession.VERIFICATION_INCOMPLETE_UNKNOWN;
+        mTestSession.reportVerificationIncomplete(reason);
+        verify(mTestCallback, times(1)).reportVerificationIncomplete(
+                eq(TEST_ID), eq(reason));
+    }
+}
diff --git a/core/tests/coretests/src/android/content/pm/verify/VerificationStatusTest.java b/core/tests/coretests/src/android/content/pm/verify/VerificationStatusTest.java
new file mode 100644
index 0000000..67d407a
--- /dev/null
+++ b/core/tests/coretests/src/android/content/pm/verify/VerificationStatusTest.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2024 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.verify;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.pm.verify.pkg.VerificationStatus;
+import android.os.Parcel;
+import android.os.PersistableBundle;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class VerificationStatusTest {
+    private static final boolean TEST_VERIFIED = true;
+    private static final int TEST_ASL_STATUS = VerificationStatus.VERIFIER_STATUS_ASL_GOOD;
+    private static final String TEST_FAILURE_MESSAGE = "test test";
+    private static final String TEST_KEY = "test key";
+    private static final String TEST_VALUE = "test value";
+    private final PersistableBundle mTestExtras = new PersistableBundle();
+    private VerificationStatus mStatus;
+
+    @Before
+    public void setUpWithBuilder() {
+        mTestExtras.putString(TEST_KEY, TEST_VALUE);
+        mStatus = new VerificationStatus.Builder()
+                .setAslStatus(TEST_ASL_STATUS)
+                .setFailureMessage(TEST_FAILURE_MESSAGE)
+                .setVerified(TEST_VERIFIED)
+                .build();
+    }
+
+    @Test
+    public void testGetters() {
+        assertThat(mStatus.isVerified()).isEqualTo(TEST_VERIFIED);
+        assertThat(mStatus.getAslStatus()).isEqualTo(TEST_ASL_STATUS);
+        assertThat(mStatus.getFailureMessage()).isEqualTo(TEST_FAILURE_MESSAGE);
+    }
+
+    @Test
+    public void testParcel() {
+        Parcel parcel = Parcel.obtain();
+        mStatus.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        VerificationStatus statusFromParcel = VerificationStatus.CREATOR.createFromParcel(parcel);
+        assertThat(statusFromParcel.isVerified()).isEqualTo(TEST_VERIFIED);
+        assertThat(statusFromParcel.getAslStatus()).isEqualTo(TEST_ASL_STATUS);
+        assertThat(statusFromParcel.getFailureMessage()).isEqualTo(TEST_FAILURE_MESSAGE);
+    }
+}
diff --git a/core/tests/coretests/src/android/content/pm/verify/VerifierServiceTest.java b/core/tests/coretests/src/android/content/pm/verify/VerifierServiceTest.java
new file mode 100644
index 0000000..7f73a1e
--- /dev/null
+++ b/core/tests/coretests/src/android/content/pm/verify/VerifierServiceTest.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2024 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.verify;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.SigningInfo;
+import android.content.pm.verify.pkg.IVerifierService;
+import android.content.pm.verify.pkg.VerificationSession;
+import android.content.pm.verify.pkg.VerifierService;
+import android.net.Uri;
+import android.os.PersistableBundle;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mockito;
+
+import java.util.ArrayList;
+
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class VerifierServiceTest {
+    private static final int TEST_ID = 100;
+    private static final int TEST_INSTALL_SESSION_ID = 33;
+    private static final String TEST_PACKAGE_NAME = "com.foo";
+    private static final Uri TEST_PACKAGE_URI = Uri.parse("test://test");
+    private static final SigningInfo TEST_SIGNING_INFO = new SigningInfo();
+    private VerifierService mService;
+    private VerificationSession mSession;
+
+    @Before
+    public void setUp() {
+        mService = Mockito.mock(VerifierService.class, Answers.CALLS_REAL_METHODS);
+        mSession = new VerificationSession(TEST_ID, TEST_INSTALL_SESSION_ID,
+                TEST_PACKAGE_NAME, TEST_PACKAGE_URI, TEST_SIGNING_INFO,
+                new ArrayList<>(),
+                new PersistableBundle(), null, null);
+    }
+
+    @Test
+    public void testBind() throws Exception {
+        Intent intent = Mockito.mock(Intent.class);
+        when(intent.getAction()).thenReturn(PackageManager.ACTION_VERIFY_PACKAGE);
+        IVerifierService binder =
+                (IVerifierService) mService.onBind(intent);
+        assertThat(binder).isNotNull();
+        binder.onPackageNameAvailable(TEST_PACKAGE_NAME);
+        verify(mService).onPackageNameAvailable(eq(TEST_PACKAGE_NAME));
+        binder.onVerificationCancelled(TEST_PACKAGE_NAME);
+        verify(mService).onVerificationCancelled(eq(TEST_PACKAGE_NAME));
+        binder.onVerificationRequired(mSession);
+        verify(mService).onVerificationRequired(eq(mSession));
+        binder.onVerificationRetry(mSession);
+        verify(mService).onVerificationRetry(eq(mSession));
+        binder.onVerificationTimeout(TEST_ID);
+        verify(mService).onVerificationTimeout(eq(TEST_ID));
+    }
+
+    @Test
+    public void testBindFailsWithWrongIntent() {
+        Intent intent = Mockito.mock(Intent.class);
+        when(intent.getAction()).thenReturn(Intent.ACTION_SEND);
+        assertThat(mService.onBind(intent)).isNull();
+    }
+}