Add public API and stub implementations for app integrity manager (service).

App integrity manager will accept rules from verifier and evaluate these rules
during install (and accept/reject the install accordingly). For more
information, see the linked bug (and the blocked bug).

Design doc: go/integrity-component-android-infra

Bug: 143689885
Change-Id: Ie965c6cc77987e06e9dea4be8fb49785a7bd08c4
Test: moving only
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 0fdb513..0c6fdc0 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -4917,6 +4917,14 @@
     public static final String APP_SEARCH_SERVICE = "app_search";
 
     /**
+     * Use with {@link #getSystemService(String)} to retrieve an
+     * {@link android.content.integrity.AppIntegrityManager}.
+     * @hide
+     */
+    @SystemApi
+    public static final String APP_INTEGRITY_SERVICE = "app_integrity";
+
+    /**
      * Determine whether the given permission is allowed for a particular
      * process and user ID running in the system.
      *
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index ca374f93..40aca0e 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -4417,6 +4417,22 @@
     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
     public static final String ACTION_VIEW_LOCUS = "android.intent.action.VIEW_LOCUS";
 
+    /**
+     * Broadcast Action: Sent to the integrity component when a package
+     * needs to be verified. The data contains the package URI along with other relevant
+     * information.
+     *
+     * <p class="note">
+     * This is a protected intent that can only be sent by the system.
+     * </p>
+     *
+     * @hide
+     */
+    @SystemApi
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_PACKAGE_NEEDS_INTEGRITY_VERIFICATION =
+            "android.intent.action.PACKAGE_NEEDS_INTEGRITY_VERIFICATION";
+
     // ---------------------------------------------------------------------
     // ---------------------------------------------------------------------
     // Standard intent categories (see addCategory()).
diff --git a/core/java/android/content/integrity/AppInstallMetadata.java b/core/java/android/content/integrity/AppInstallMetadata.java
new file mode 100644
index 0000000..c963475
--- /dev/null
+++ b/core/java/android/content/integrity/AppInstallMetadata.java
@@ -0,0 +1,182 @@
+/*
+ * 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 android.content.integrity;
+
+import static com.android.internal.util.Preconditions.checkArgument;
+import static com.android.internal.util.Preconditions.checkNotNull;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * The app install metadata.
+ *
+ * <p>The integrity component retrieves metadata for app installs from package manager, passing it
+ * to the rule evaluation engine to evaluate the metadata against the rules.
+ *
+ * <p>Instances of this class are immutable.
+ *
+ * @hide
+ */
+@SystemApi
+@VisibleForTesting
+public final class AppInstallMetadata {
+    private final String mPackageName;
+    // Raw string encoding for the SHA-256 hash of the certificate of the app.
+    private final String mAppCertificate;
+    private final String mInstallerName;
+    // Raw string encoding for the SHA-256 hash of the certificate of the installer.
+    private final String mInstallerCertificate;
+    private final int mVersionCode;
+    private final boolean mIsPreInstalled;
+
+    private AppInstallMetadata(Builder builder) {
+        this.mPackageName = builder.mPackageName;
+        this.mAppCertificate = builder.mAppCertificate;
+        this.mInstallerName = builder.mInstallerName;
+        this.mInstallerCertificate = builder.mInstallerCertificate;
+        this.mVersionCode = builder.mVersionCode;
+        this.mIsPreInstalled = builder.mIsPreInstalled;
+    }
+
+    @NonNull
+    public String getPackageName() {
+        return mPackageName;
+    }
+
+    @NonNull
+    public String getAppCertificate() {
+        return mAppCertificate;
+    }
+
+    @Nullable
+    public String getInstallerName() {
+        return mInstallerName;
+    }
+
+    @Nullable
+    public String getInstallerCertificate() {
+        return mInstallerCertificate;
+    }
+
+    /** @see AppInstallMetadata.Builder#setVersionCode(int) */
+    public int getVersionCode() {
+        return mVersionCode;
+    }
+
+    /** @see AppInstallMetadata.Builder#setIsPreInstalled(boolean) */
+    public boolean isPreInstalled() {
+        return mIsPreInstalled;
+    }
+
+    /** Builder class for constructing {@link AppInstallMetadata} objects. */
+    public static final class Builder {
+        private String mPackageName;
+        private String mAppCertificate;
+        private String mInstallerName;
+        private String mInstallerCertificate;
+        private int mVersionCode;
+        private boolean mIsPreInstalled;
+
+        /**
+         * Set package name of the app to be installed.
+         *
+         * @see AppInstallMetadata#getPackageName()
+         */
+        @NonNull
+        public Builder setPackageName(@NonNull String packageName) {
+            this.mPackageName = checkNotNull(packageName);
+            return this;
+        }
+
+        /**
+         * Set certificate of the app to be installed.
+         *
+         * <p>It is represented as the raw string encoding for the SHA-256 hash of the certificate
+         * of the app.
+         *
+         * @see AppInstallMetadata#getAppCertificate()
+         */
+        @NonNull
+        public Builder setAppCertificate(@NonNull String appCertificate) {
+            this.mAppCertificate = checkNotNull(appCertificate);
+            return this;
+        }
+
+        /**
+         * Set name of the installer installing the app.
+         *
+         * @see AppInstallMetadata#getInstallerName()
+         */
+        @NonNull
+        public Builder setInstallerName(@NonNull String installerName) {
+            this.mInstallerName = checkNotNull(installerName);
+            return this;
+        }
+
+        /**
+         * Set certificate of the installer installing the app.
+         *
+         * <p>It is represented as the raw string encoding for the SHA-256 hash of the certificate
+         * of the installer.
+         *
+         * @see AppInstallMetadata#getInstallerCertificate()
+         */
+        @NonNull
+        public Builder setInstallerCertificate(@NonNull String installerCertificate) {
+            this.mInstallerCertificate = checkNotNull(installerCertificate);
+            return this;
+        }
+
+        /**
+         * Set version code of the app to be installed.
+         *
+         * @see AppInstallMetadata#getVersionCode()
+         */
+        @NonNull
+        public Builder setVersionCode(int versionCode) {
+            this.mVersionCode = versionCode;
+            return this;
+        }
+
+        /**
+         * Set whether the app is pre-installed on the device or not.
+         *
+         * @see AppInstallMetadata#isPreInstalled()
+         */
+        @NonNull
+        public Builder setIsPreInstalled(boolean isPreInstalled) {
+            this.mIsPreInstalled = isPreInstalled;
+            return this;
+        }
+
+        /**
+         * Build {@link AppInstallMetadata}.
+         *
+         * @throws IllegalArgumentException if package name or app certificate is null
+         */
+        @NonNull
+        public AppInstallMetadata build() {
+            checkArgument(mPackageName != null);
+            checkArgument(mAppCertificate != null);
+            return new AppInstallMetadata(this);
+        }
+    }
+}
diff --git a/core/java/android/content/integrity/AppIntegrityManager.java b/core/java/android/content/integrity/AppIntegrityManager.java
new file mode 100644
index 0000000..e53ef66
--- /dev/null
+++ b/core/java/android/content/integrity/AppIntegrityManager.java
@@ -0,0 +1,103 @@
+/*
+ * 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 android.content.integrity;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.content.Context;
+import android.content.IntentSender;
+import android.content.pm.ParceledListSlice;
+import android.os.RemoteException;
+
+/**
+ * Class for pushing rules used to check the integrity of app installs.
+ *
+ * <p>Note: applications using methods of this class must be a system app and have their package
+ * name whitelisted as an integrity rule provider. Otherwise a {@link SecurityException} will be
+ * thrown.
+ *
+ * @hide
+ */
+@SystemApi
+@SystemService(Context.APP_INTEGRITY_SERVICE)
+public class AppIntegrityManager {
+
+    /** The operation succeeded. */
+    public static final int STATUS_SUCCESS = 0;
+
+    /** The operation failed. */
+    public static final int STATUS_FAILURE = 1;
+
+    /**
+     * Current status of an operation. Will be one of {@link #STATUS_SUCCESS}, {@link
+     * #STATUS_FAILURE}.
+     *
+     * <p>More information about a status may be available through additional extras; see the
+     * individual status documentation for details.
+     *
+     * @see android.content.Intent#getIntExtra(String, int)
+     */
+    public static final String EXTRA_STATUS = "android.content.integrity.extra.STATUS";
+
+    IAppIntegrityManager mManager;
+
+    /** @hide */
+    public AppIntegrityManager(IAppIntegrityManager manager) {
+        mManager = manager;
+    }
+
+    /**
+     * Update the rules to evaluate during install time.
+     *
+     * @param updateRequest request containing the data of the rule set update
+     * @param statusReceiver Called when the state of the session changes. Intents sent to this
+     *     receiver contain {@link #EXTRA_STATUS}. Refer to the individual status codes on how to
+     *     handle them.
+     */
+    public void updateRuleSet(
+            @NonNull RuleSet updateRequest, @NonNull IntentSender statusReceiver) {
+        try {
+            mManager.updateRuleSet(
+                    updateRequest.getVersion(),
+                    new ParceledListSlice<>(updateRequest.getRules()),
+                    statusReceiver);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    /** Get the current version of the rule set. */
+    @NonNull
+    public String getCurrentRuleSetVersion() {
+        try {
+            return mManager.getCurrentRuleSetVersion();
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    /** Get the name of the package that provided the current rule set. */
+    @NonNull
+    public String getCurrentRuleSetProvider() {
+        try {
+            return mManager.getCurrentRuleSetProvider();
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+}
diff --git a/core/java/android/content/integrity/AtomicFormula.java b/core/java/android/content/integrity/AtomicFormula.java
new file mode 100644
index 0000000..c8e164f
--- /dev/null
+++ b/core/java/android/content/integrity/AtomicFormula.java
@@ -0,0 +1,518 @@
+/*
+ * 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 android.content.integrity;
+
+import static com.android.internal.util.Preconditions.checkArgument;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * Represents a simple formula consisting of an app install metadata field and a value.
+ *
+ * <p>Instances of this class are immutable.
+ *
+ * @hide
+ */
+@SystemApi
+@VisibleForTesting
+public abstract class AtomicFormula implements Formula {
+
+    private static final String TAG = "AtomicFormula";
+
+    @IntDef(
+            value = {
+                PACKAGE_NAME,
+                APP_CERTIFICATE,
+                INSTALLER_NAME,
+                INSTALLER_CERTIFICATE,
+                VERSION_CODE,
+                PRE_INSTALLED,
+            })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Key {}
+
+    @IntDef(value = {EQ, LT, LE, GT, GE})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Operator {}
+
+    /**
+     * Package name of the app.
+     *
+     * <p>Can only be used in {@link StringAtomicFormula}.
+     */
+    public static final int PACKAGE_NAME = 0;
+
+    /**
+     * SHA-256 of the app certificate of the app.
+     *
+     * <p>Can only be used in {@link StringAtomicFormula}.
+     */
+    public static final int APP_CERTIFICATE = 1;
+
+    /**
+     * Package name of the installer. Will be empty string if installed by the system (e.g., adb).
+     *
+     * <p>Can only be used in {@link StringAtomicFormula}.
+     */
+    public static final int INSTALLER_NAME = 2;
+
+    /**
+     * SHA-256 of the cert of the installer. Will be empty string if installed by the system (e.g.,
+     * adb).
+     *
+     * <p>Can only be used in {@link StringAtomicFormula}.
+     */
+    public static final int INSTALLER_CERTIFICATE = 3;
+
+    /**
+     * Version code of the app.
+     *
+     * <p>Can only be used in {@link IntAtomicFormula}.
+     */
+    public static final int VERSION_CODE = 4;
+
+    /**
+     * If the app is pre-installed on the device.
+     *
+     * <p>Can only be used in {@link BooleanAtomicFormula}.
+     */
+    public static final int PRE_INSTALLED = 5;
+
+    public static final int EQ = 0;
+    public static final int LT = 1;
+    public static final int LE = 2;
+    public static final int GT = 3;
+    public static final int GE = 4;
+
+    private final @Key int mKey;
+
+    public AtomicFormula(@Key int key) {
+        checkArgument(isValidKey(key), String.format("Unknown key: %d", key));
+        mKey = key;
+    }
+
+    /** An {@link AtomicFormula} with an key and int value. */
+    public static final class IntAtomicFormula extends AtomicFormula implements Parcelable {
+        private final int mValue;
+        private final @Operator int mOperator;
+
+        /**
+         * Constructs a new {@link IntAtomicFormula}.
+         *
+         * <p>This formula will hold if and only if the corresponding information of an install
+         * specified by {@code key} is of the correct relationship to {@code value} as specified by
+         * {@code operator}.
+         *
+         * @throws IllegalArgumentException if {@code key} cannot be used with integer value
+         */
+        public IntAtomicFormula(@Key int key, @Operator int operator, int value) {
+            super(key);
+            checkArgument(
+                    key == VERSION_CODE,
+                    String.format("Key %s cannot be used with IntAtomicFormula", keyToString(key)));
+            checkArgument(isValidOperator(operator),
+                    String.format("Unknown operator: %d", operator));
+            mOperator = operator;
+            mValue = value;
+        }
+
+        IntAtomicFormula(Parcel in) {
+            super(in.readInt());
+            mValue = in.readInt();
+            mOperator = in.readInt();
+        }
+
+        @NonNull
+        public static final Creator<IntAtomicFormula> CREATOR =
+                new Creator<IntAtomicFormula>() {
+                    @Override
+                    public IntAtomicFormula createFromParcel(Parcel in) {
+                        return new IntAtomicFormula(in);
+                    }
+
+                    @Override
+                    public IntAtomicFormula[] newArray(int size) {
+                        return new IntAtomicFormula[size];
+                    }
+                };
+
+        @Override
+        public boolean isSatisfied(@NonNull AppInstallMetadata appInstallMetadata) {
+            int metadataValue = getMetadataValueByKey(appInstallMetadata);
+            switch (mOperator) {
+                case EQ:
+                    return metadataValue == mValue;
+                case LE:
+                    return metadataValue <= mValue;
+                case LT:
+                    return metadataValue < mValue;
+                case GE:
+                    return metadataValue >= mValue;
+                case GT:
+                    return metadataValue > mValue;
+                default:
+                    Slog.i(TAG, String.format("Unexpected operator %d", mOperator));
+                    return false;
+            }
+        }
+
+        @Override
+        public int getTag() {
+            return Formula.INT_ATOMIC_FORMULA_TAG;
+        }
+
+        @Override
+        public String toString() {
+            return String.format(
+                    "(%s %s %s)", keyToString(getKey()), operatorToString(mOperator), mValue);
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+            IntAtomicFormula that = (IntAtomicFormula) o;
+            return getKey() == that.getKey()
+                    && mValue == that.mValue
+                    && mOperator == that.mOperator;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(getKey(), mOperator, mValue);
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
+            dest.writeInt(getKey());
+            dest.writeInt(mValue);
+            dest.writeInt(mOperator);
+        }
+
+        public int getValue() {
+            return mValue;
+        }
+
+        public int getOperator() {
+            return mOperator;
+        }
+
+        private int getMetadataValueByKey(AppInstallMetadata appInstallMetadata) {
+            switch (getKey()) {
+                case VERSION_CODE:
+                    return appInstallMetadata.getVersionCode();
+                default:
+                    throw new IllegalStateException(
+                            "Unexpected key in IntAtomicFormula" + getKey());
+            }
+        }
+
+        private static boolean isValidOperator(int operator) {
+            return operator == EQ
+                    || operator == LT
+                    || operator == LE
+                    || operator == GT
+                    || operator == GE;
+        }
+    }
+
+    /** An {@link AtomicFormula} with a key and string value. */
+    public static final class StringAtomicFormula extends AtomicFormula implements Parcelable {
+        private final String mValue;
+        // Indicates whether the value is the actual value or the hashed value.
+        private final boolean mIsHashedValue;
+
+        /**
+         * Constructs a new {@link StringAtomicFormula}.
+         *
+         * <p>This formula will hold if and only if the corresponding information of an install
+         * specified by {@code key} equals {@code value}.
+         *
+         * @throws IllegalArgumentException if {@code key} cannot be used with string value
+         */
+        public StringAtomicFormula(@Key int key, @NonNull String value, boolean isHashedValue) {
+            super(key);
+            mIsHashedValue = isHashedValue;
+            checkArgument(
+                    key == PACKAGE_NAME
+                            || key == APP_CERTIFICATE
+                            || key == INSTALLER_CERTIFICATE
+                            || key == INSTALLER_NAME,
+                    String.format(
+                            "Key %s cannot be used with StringAtomicFormula", keyToString(key)));
+            mValue = value;
+        }
+
+        StringAtomicFormula(Parcel in) {
+            super(in.readInt());
+            mValue = in.readStringNoHelper();
+            mIsHashedValue = in.readByte() != 0;
+        }
+
+        @NonNull
+        public static final Creator<StringAtomicFormula> CREATOR =
+                new Creator<StringAtomicFormula>() {
+                    @Override
+                    public StringAtomicFormula createFromParcel(Parcel in) {
+                        return new StringAtomicFormula(in);
+                    }
+
+                    @Override
+                    public StringAtomicFormula[] newArray(int size) {
+                        return new StringAtomicFormula[size];
+                    }
+                };
+
+        @Override
+        public boolean isSatisfied(@NonNull AppInstallMetadata appInstallMetadata) {
+            String metadataValue = getMetadataValueByKey(appInstallMetadata);
+            return metadataValue.equals(mValue);
+        }
+
+        @Override
+        public int getTag() {
+            return Formula.STRING_ATOMIC_FORMULA_TAG;
+        }
+
+        @Override
+        public String toString() {
+            return String.format("(%s %s %s)", keyToString(getKey()), operatorToString(EQ), mValue);
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+            StringAtomicFormula that = (StringAtomicFormula) o;
+            return getKey() == that.getKey() && Objects.equals(mValue, that.mValue);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(getKey(), mValue);
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
+            dest.writeInt(getKey());
+            dest.writeStringNoHelper(mValue);
+            dest.writeByte((byte) (mIsHashedValue ? 1 : 0));
+        }
+
+        @NonNull
+        public String getValue() {
+            return mValue;
+        }
+
+        public boolean getIsHashedValue() {
+            return mIsHashedValue;
+        }
+
+        private String getMetadataValueByKey(AppInstallMetadata appInstallMetadata) {
+            switch (getKey()) {
+                case PACKAGE_NAME:
+                    return appInstallMetadata.getPackageName();
+                case APP_CERTIFICATE:
+                    return appInstallMetadata.getAppCertificate();
+                case INSTALLER_CERTIFICATE:
+                    return appInstallMetadata.getInstallerCertificate();
+                case INSTALLER_NAME:
+                    return appInstallMetadata.getInstallerName();
+                default:
+                    throw new IllegalStateException(
+                            "Unexpected key in StringAtomicFormula: " + getKey());
+            }
+        }
+    }
+
+    /** An {@link AtomicFormula} with a key and boolean value. */
+    public static final class BooleanAtomicFormula extends AtomicFormula implements Parcelable {
+        private final boolean mValue;
+
+        /**
+         * Constructs a new {@link BooleanAtomicFormula}.
+         *
+         * <p>This formula will hold if and only if the corresponding information of an install
+         * specified by {@code key} equals {@code value}.
+         *
+         * @throws IllegalArgumentException if {@code key} cannot be used with boolean value
+         */
+        public BooleanAtomicFormula(@Key int key, boolean value) {
+            super(key);
+            checkArgument(
+                    key == PRE_INSTALLED,
+                    String.format(
+                            "Key %s cannot be used with BooleanAtomicFormula", keyToString(key)));
+            mValue = value;
+        }
+
+        BooleanAtomicFormula(Parcel in) {
+            super(in.readInt());
+            mValue = in.readByte() != 0;
+        }
+
+        @NonNull
+        public static final Creator<BooleanAtomicFormula> CREATOR =
+                new Creator<BooleanAtomicFormula>() {
+                    @Override
+                    public BooleanAtomicFormula createFromParcel(Parcel in) {
+                        return new BooleanAtomicFormula(in);
+                    }
+
+                    @Override
+                    public BooleanAtomicFormula[] newArray(int size) {
+                        return new BooleanAtomicFormula[size];
+                    }
+                };
+
+        @Override
+        public boolean isSatisfied(@NonNull AppInstallMetadata appInstallMetadata) {
+            boolean metadataValue = getMetadataValueByKey(appInstallMetadata);
+            return metadataValue == mValue;
+        }
+
+        @Override
+        public int getTag() {
+            return Formula.BOOLEAN_ATOMIC_FORMULA_TAG;
+        }
+
+        @Override
+        public String toString() {
+            return String.format("(%s %s %s)", keyToString(getKey()), operatorToString(EQ), mValue);
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+            BooleanAtomicFormula that = (BooleanAtomicFormula) o;
+            return getKey() == that.getKey() && mValue == that.mValue;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(getKey(), mValue);
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
+            dest.writeInt(getKey());
+            dest.writeByte((byte) (mValue ? 1 : 0));
+        }
+
+        public boolean getValue() {
+            return mValue;
+        }
+
+        private boolean getMetadataValueByKey(AppInstallMetadata appInstallMetadata) {
+            switch (getKey()) {
+                case PRE_INSTALLED:
+                    return appInstallMetadata.isPreInstalled();
+                default:
+                    throw new IllegalStateException(
+                            "Unexpected key in BooleanAtomicFormula: " + getKey());
+            }
+        }
+    }
+
+    public int getKey() {
+        return mKey;
+    }
+
+    static String keyToString(int key) {
+        switch (key) {
+            case PACKAGE_NAME:
+                return "PACKAGE_NAME";
+            case APP_CERTIFICATE:
+                return "APP_CERTIFICATE";
+            case VERSION_CODE:
+                return "VERSION_CODE";
+            case INSTALLER_NAME:
+                return "INSTALLER_NAME";
+            case INSTALLER_CERTIFICATE:
+                return "INSTALLER_CERTIFICATE";
+            case PRE_INSTALLED:
+                return "PRE_INSTALLED";
+            default:
+                throw new IllegalArgumentException("Unknown key " + key);
+        }
+    }
+
+    static String operatorToString(int op) {
+        switch (op) {
+            case EQ:
+                return "EQ";
+            case LT:
+                return "LT";
+            case LE:
+                return "LE";
+            case GT:
+                return "GT";
+            case GE:
+                return "GE";
+            default:
+                throw new IllegalArgumentException("Unknown operator " + op);
+        }
+    }
+
+    private static boolean isValidKey(int key) {
+        return key == PACKAGE_NAME
+                || key == APP_CERTIFICATE
+                || key == VERSION_CODE
+                || key == INSTALLER_NAME
+                || key == INSTALLER_CERTIFICATE
+                || key == PRE_INSTALLED;
+    }
+}
diff --git a/core/java/android/content/integrity/CompoundFormula.java b/core/java/android/content/integrity/CompoundFormula.java
new file mode 100644
index 0000000..53a9953
--- /dev/null
+++ b/core/java/android/content/integrity/CompoundFormula.java
@@ -0,0 +1,223 @@
+/*
+ * 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 android.content.integrity;
+
+import static com.android.internal.util.Preconditions.checkArgument;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Represents a compound formula formed by joining other simple and complex formulas with boolean
+ * connectors.
+ *
+ * <p>Instances of this class are immutable.
+ *
+ * @hide
+ */
+@SystemApi
+@VisibleForTesting
+public final class CompoundFormula implements Formula, Parcelable {
+    private static final String TAG = "OpenFormula";
+
+    @IntDef(
+            value = {
+                AND, OR, NOT,
+            })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Connector {}
+
+    /** Boolean AND operator. */
+    public static final int AND = 0;
+
+    /** Boolean OR operator. */
+    public static final int OR = 1;
+
+    /** Boolean NOT operator. */
+    public static final int NOT = 2;
+
+    private final @Connector int mConnector;
+    private final @NonNull List<Formula> mFormulas;
+
+    @NonNull
+    public static final Creator<CompoundFormula> CREATOR =
+            new Creator<CompoundFormula>() {
+                @Override
+                public CompoundFormula createFromParcel(Parcel in) {
+                    return new CompoundFormula(in);
+                }
+
+                @Override
+                public CompoundFormula[] newArray(int size) {
+                    return new CompoundFormula[size];
+                }
+            };
+
+    /**
+     * Create a new formula from operator and operands.
+     *
+     * @throws IllegalArgumentException if the number of operands is not matching the requirements
+     *     for that operator (at least 2 for {@link #AND} and {@link #OR}, 1 for {@link #NOT}).
+     */
+    public CompoundFormula(@Connector int connector, @NonNull List<Formula> formulas) {
+        checkArgument(
+                isValidConnector(connector), String.format("Unknown connector: %d", connector));
+        validateFormulas(connector, formulas);
+        this.mConnector = connector;
+        this.mFormulas = Collections.unmodifiableList(formulas);
+    }
+
+    CompoundFormula(Parcel in) {
+        mConnector = in.readInt();
+        int length = in.readInt();
+        checkArgument(length >= 0, "Must have non-negative length. Got " + length);
+        mFormulas = new ArrayList<>(length);
+        for (int i = 0; i < length; i++) {
+            mFormulas.add(Formula.readFromParcel(in));
+        }
+        validateFormulas(mConnector, mFormulas);
+    }
+
+    public @Connector int getConnector() {
+        return mConnector;
+    }
+
+    @NonNull
+    public List<Formula> getFormulas() {
+        return mFormulas;
+    }
+
+    @Override
+    public boolean isSatisfied(@NonNull AppInstallMetadata appInstallMetadata) {
+        switch (mConnector) {
+            case NOT:
+                return !mFormulas.get(0).isSatisfied(appInstallMetadata);
+            case AND:
+                return mFormulas.stream()
+                        .allMatch(formula -> formula.isSatisfied(appInstallMetadata));
+            case OR:
+                return mFormulas.stream()
+                        .anyMatch(formula -> formula.isSatisfied(appInstallMetadata));
+            default:
+                Slog.i(TAG, "Unknown connector " + mConnector);
+                return false;
+        }
+    }
+
+    @Override
+    public int getTag() {
+        return Formula.COMPOUND_FORMULA_TAG;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        if (mFormulas.size() == 1) {
+            sb.append(String.format("%s ", connectorToString(mConnector)));
+            sb.append(mFormulas.get(0).toString());
+        } else {
+            for (int i = 0; i < mFormulas.size(); i++) {
+                if (i > 0) {
+                    sb.append(String.format(" %s ", connectorToString(mConnector)));
+                }
+                sb.append(mFormulas.get(i).toString());
+            }
+        }
+        return sb.toString();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        CompoundFormula that = (CompoundFormula) o;
+        return mConnector == that.mConnector && mFormulas.equals(that.mFormulas);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mConnector, mFormulas);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mConnector);
+        dest.writeInt(mFormulas.size());
+        for (Formula formula : mFormulas) {
+            Formula.writeToParcel(formula, dest, flags);
+        }
+    }
+
+    private static void validateFormulas(@Connector int connector, List<Formula> formulas) {
+        switch (connector) {
+            case AND:
+            case OR:
+                checkArgument(
+                        formulas.size() >= 2,
+                        String.format(
+                                "Connector %s must have at least 2 formulas",
+                                connectorToString(connector)));
+                break;
+            case NOT:
+                checkArgument(
+                        formulas.size() == 1,
+                        String.format(
+                                "Connector %s must have 1 formula only",
+                                connectorToString(connector)));
+                break;
+        }
+    }
+
+    private static String connectorToString(int connector) {
+        switch (connector) {
+            case AND:
+                return "AND";
+            case OR:
+                return "OR";
+            case NOT:
+                return "NOT";
+            default:
+                throw new IllegalArgumentException("Unknown connector " + connector);
+        }
+    }
+
+    private static boolean isValidConnector(int connector) {
+        return connector == AND || connector == OR || connector == NOT;
+    }
+}
diff --git a/core/java/android/content/integrity/Formula.java b/core/java/android/content/integrity/Formula.java
new file mode 100644
index 0000000..030ff6b
--- /dev/null
+++ b/core/java/android/content/integrity/Formula.java
@@ -0,0 +1,102 @@
+/*
+ * 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 android.content.integrity;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.content.integrity.AtomicFormula.BooleanAtomicFormula;
+import android.content.integrity.AtomicFormula.IntAtomicFormula;
+import android.content.integrity.AtomicFormula.StringAtomicFormula;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Represents a rule logic/content.
+ *
+ * @hide
+ */
+@SystemApi
+@VisibleForTesting
+public interface Formula {
+    @IntDef(
+            value = {
+                    COMPOUND_FORMULA_TAG,
+                    STRING_ATOMIC_FORMULA_TAG,
+                    INT_ATOMIC_FORMULA_TAG,
+                    BOOLEAN_ATOMIC_FORMULA_TAG
+            })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Tag {}
+
+    int COMPOUND_FORMULA_TAG = 0;
+    int STRING_ATOMIC_FORMULA_TAG = 1;
+    int INT_ATOMIC_FORMULA_TAG = 2;
+    int BOOLEAN_ATOMIC_FORMULA_TAG = 3;
+
+    /**
+     * Returns if this formula can be satisfied by substituting the corresponding information of
+     * {@code appInstallMetadata} into the formula.
+     */
+    boolean isSatisfied(@NonNull AppInstallMetadata appInstallMetadata);
+
+    /** Returns the tag that identifies the current class. */
+    @Tag int getTag();
+
+    /**
+     * Write a {@link Formula} to {@link android.os.Parcel}.
+     *
+     * <p>This helper method is needed because non-final class/interface are not allowed to be
+     * {@link Parcelable}.
+     *
+     * @throws IllegalArgumentException if {@link Formula} is not a recognized subclass
+     */
+    static void writeToParcel(@NonNull Formula formula, @NonNull Parcel dest, int flags) {
+        dest.writeInt(formula.getTag());
+        ((Parcelable) formula).writeToParcel(dest, flags);
+    }
+
+    /**
+     * Read a {@link Formula} from a {@link android.os.Parcel}.
+     *
+     * <p>We need this (hacky) helper method because non-final class/interface cannot be {@link
+     * Parcelable} (api lint error).
+     *
+     * @throws IllegalArgumentException if the parcel cannot be parsed
+     */
+    @NonNull
+    static Formula readFromParcel(@NonNull Parcel in) {
+        int tag = in.readInt();
+        switch (tag) {
+            case COMPOUND_FORMULA_TAG:
+                return CompoundFormula.CREATOR.createFromParcel(in);
+            case STRING_ATOMIC_FORMULA_TAG:
+                return StringAtomicFormula.CREATOR.createFromParcel(in);
+            case INT_ATOMIC_FORMULA_TAG:
+                return IntAtomicFormula.CREATOR.createFromParcel(in);
+            case BOOLEAN_ATOMIC_FORMULA_TAG:
+                return BooleanAtomicFormula.CREATOR.createFromParcel(in);
+            default:
+                throw new IllegalArgumentException("Unknown formula tag " + tag);
+        }
+    }
+}
diff --git a/core/java/android/content/integrity/IAppIntegrityManager.aidl b/core/java/android/content/integrity/IAppIntegrityManager.aidl
new file mode 100644
index 0000000..6b73fd7
--- /dev/null
+++ b/core/java/android/content/integrity/IAppIntegrityManager.aidl
@@ -0,0 +1,28 @@
+/*
+**
+** Copyright 2019, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+package android.content.integrity;
+
+import android.content.integrity.Rule;
+import android.content.IntentSender;
+import android.content.pm.ParceledListSlice;
+
+/** @hide */
+interface IAppIntegrityManager {
+    void updateRuleSet(String version, in ParceledListSlice<Rule> rules, in IntentSender statusReceiver);
+    String getCurrentRuleSetVersion();
+    String getCurrentRuleSetProvider();
+}
diff --git a/core/java/android/content/integrity/Rule.aidl b/core/java/android/content/integrity/Rule.aidl
new file mode 100644
index 0000000..a6634ee
--- /dev/null
+++ b/core/java/android/content/integrity/Rule.aidl
@@ -0,0 +1,19 @@
+/*
+**
+** Copyright 2007, 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.integrity;
+
+parcelable Rule;
diff --git a/core/java/android/content/integrity/Rule.java b/core/java/android/content/integrity/Rule.java
new file mode 100644
index 0000000..914f147
--- /dev/null
+++ b/core/java/android/content/integrity/Rule.java
@@ -0,0 +1,147 @@
+/*
+ * 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 android.content.integrity;
+
+import static com.android.internal.util.Preconditions.checkArgument;
+import static com.android.internal.util.Preconditions.checkNotNull;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * Represent rules to be used in the rule evaluation engine to match against app installs.
+ *
+ * <p>Instances of this class are immutable.
+ *
+ * @hide
+ */
+@SystemApi
+@VisibleForTesting
+public final class Rule implements Parcelable {
+
+    @IntDef(
+            value = {
+                DENY,
+                FORCE_ALLOW,
+            })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Effect {}
+
+    /** If this rule matches the install, the install should be denied. */
+    public static final int DENY = 0;
+
+    /**
+     * If this rule matches the install, the install will be allowed regardless of other matched
+     * rules.
+     */
+    public static final int FORCE_ALLOW = 1;
+
+    private final @NonNull Formula mFormula;
+    private final @Effect int mEffect;
+
+    public Rule(@NonNull Formula formula, @Effect int effect) {
+        checkArgument(isValidEffect(effect), String.format("Unknown effect: %d", effect));
+        this.mFormula = checkNotNull(formula);
+        this.mEffect = effect;
+    }
+
+    Rule(Parcel in) {
+        mFormula = Formula.readFromParcel(in);
+        mEffect = in.readInt();
+    }
+
+    @NonNull
+    public static final Creator<Rule> CREATOR =
+            new Creator<Rule>() {
+                @Override
+                public Rule createFromParcel(Parcel in) {
+                    return new Rule(in);
+                }
+
+                @Override
+                public Rule[] newArray(int size) {
+                    return new Rule[size];
+                }
+            };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        Formula.writeToParcel(mFormula, dest, flags);
+        dest.writeInt(mEffect);
+    }
+
+    @NonNull
+    public Formula getFormula() {
+        return mFormula;
+    }
+
+    public @Effect int getEffect() {
+        return mEffect;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("Rule: %s, %s", mFormula, effectToString(mEffect));
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        Rule that = (Rule) o;
+        return mEffect == that.mEffect && Objects.equals(mFormula, that.mFormula);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mFormula, mEffect);
+    }
+
+    private static String effectToString(int effect) {
+        switch (effect) {
+            case DENY:
+                return "DENY";
+            case FORCE_ALLOW:
+                return "FORCE_ALLOW";
+            default:
+                throw new IllegalArgumentException("Unknown effect " + effect);
+        }
+    }
+
+    private static boolean isValidEffect(int effect) {
+        return effect == DENY
+                || effect == FORCE_ALLOW;
+    }
+}
diff --git a/core/java/android/content/integrity/RuleSet.java b/core/java/android/content/integrity/RuleSet.java
new file mode 100644
index 0000000..a78f8c9
--- /dev/null
+++ b/core/java/android/content/integrity/RuleSet.java
@@ -0,0 +1,92 @@
+/*
+ * 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 android.content.integrity;
+
+import static com.android.internal.util.Preconditions.checkNotNull;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Immutable data class encapsulating all parameters of a rule set.
+ *
+ * @hide
+ */
+@SystemApi
+public class RuleSet {
+    private final String mVersion;
+    private final List<Rule> mRules;
+
+    private RuleSet(String version, List<Rule> rules) {
+        mVersion = version;
+        mRules = Collections.unmodifiableList(rules);
+    }
+
+    /** @see Builder#setVersion(String). */
+    @NonNull
+    public String getVersion() {
+        return mVersion;
+    }
+
+    /** @see Builder#addRules(List). */
+    @NonNull
+    public List<Rule> getRules() {
+        return mRules;
+    }
+
+    /** Builder class for RuleSetUpdateRequest. */
+    public static class Builder {
+        private String mVersion;
+        private List<Rule> mRules;
+
+        public Builder() {
+            mRules = new ArrayList<>();
+        }
+
+        /**
+         * Set a version string to identify this rule set. This can be retrieved by {@link
+         * AppIntegrityManager#getCurrentRuleSetVersion()}.
+         */
+        @NonNull
+        public Builder setVersion(@NonNull String version) {
+            mVersion = version;
+            return this;
+        }
+
+        /** Add the rules to include. */
+        @NonNull
+        public Builder addRules(@NonNull List<Rule> rules) {
+            mRules.addAll(rules);
+            return this;
+        }
+
+        /**
+         * Builds a {@link RuleSet}.
+         *
+         * @throws IllegalArgumentException if version is null
+         */
+        @NonNull
+        public RuleSet build() {
+            checkNotNull(mVersion);
+            return new RuleSet(mVersion, mRules);
+        }
+    }
+}