Loading services/core/java/com/android/server/integrity/engine/RuleEvaluationEngine.java +2 −16 Original line number Diff line number Diff line Loading @@ -16,8 +16,6 @@ package com.android.server.integrity.engine; import android.util.Slog; import com.android.server.integrity.model.AppInstallMetadata; import com.android.server.integrity.model.IntegrityCheckResult; import com.android.server.integrity.model.Rule; Loading Loading @@ -53,23 +51,11 @@ public final class RuleEvaluationEngine { * * @param appInstallMetadata Metadata of the app to be installed, and to evaluate the rules * against. * @return A rule matching the metadata. If there are multiple matching rules, returns any. If * no rules are matching, returns {@link Rule#EMPTY}. * @return result of the integrity check */ public IntegrityCheckResult evaluate(AppInstallMetadata appInstallMetadata) { List<Rule> rules = loadRules(appInstallMetadata); Rule matchedRule = RuleEvaluator.evaluateRules(rules, appInstallMetadata); if (matchedRule == Rule.EMPTY) { return IntegrityCheckResult.allow(); } else { switch (matchedRule.getEffect()) { case DENY: return IntegrityCheckResult.deny(matchedRule); default: Slog.e(TAG, "Matched a non-DENY rule: " + matchedRule); return IntegrityCheckResult.allow(); } } return RuleEvaluator.evaluateRules(rules, appInstallMetadata); } private List<Rule> loadRules(AppInstallMetadata appInstallMetadata) { Loading services/core/java/com/android/server/integrity/engine/RuleEvaluator.java +33 −54 Original line number Diff line number Diff line Loading @@ -16,14 +16,20 @@ package com.android.server.integrity.engine; import static com.android.server.integrity.model.Rule.DENY; import static com.android.server.integrity.model.Rule.FORCE_ALLOW; import android.annotation.NonNull; import android.util.Slog; import com.android.server.integrity.model.AppInstallMetadata; import com.android.server.integrity.model.AtomicFormula; import com.android.server.integrity.model.Formula; import com.android.server.integrity.model.IntegrityCheckResult; import com.android.server.integrity.model.OpenFormula; import com.android.server.integrity.model.Rule; import java.util.ArrayList; import java.util.List; /** Loading @@ -43,64 +49,37 @@ final class RuleEvaluator { * @param rules The list of rules to evaluate. * @param appInstallMetadata Metadata of the app to be installed, and to evaluate the rules * against. * @return A rule matching the metadata. If there are multiple matching rules, returns any. If * no rules are matching, returns {@link Rule#EMPTY}. * @return result of the integrity check */ static Rule evaluateRules(List<Rule> rules, AppInstallMetadata appInstallMetadata) { @NonNull static IntegrityCheckResult evaluateRules( List<Rule> rules, AppInstallMetadata appInstallMetadata) { List<Rule> matchedRules = new ArrayList<>(); for (Rule rule : rules) { if (isConjunctionOfFormulas(rule.getFormula()) && isMatch(rule, appInstallMetadata)) { return rule; } if (isConjunctionOfFormulas(rule.getFormula()) && rule.getFormula().isSatisfied(appInstallMetadata)) { matchedRules.add(rule); } return Rule.EMPTY; } /** * Match a rule against app install metadata. */ private static boolean isMatch(Rule rule, AppInstallMetadata appInstallMetadata) { return isMatch(rule.getFormula(), appInstallMetadata); boolean denied = false; Rule denyRule = null; for (Rule rule : matchedRules) { switch (rule.getEffect()) { case DENY: if (!denied) { denied = true; denyRule = rule; } private static boolean isMatch(Formula formula, AppInstallMetadata appInstallMetadata) { if (formula instanceof AtomicFormula) { AtomicFormula atomicFormula = (AtomicFormula) formula; switch (atomicFormula.getKey()) { case PACKAGE_NAME: return atomicFormula.isMatch(appInstallMetadata.getPackageName()); case APP_CERTIFICATE: return atomicFormula.isMatch(appInstallMetadata.getAppCertificate()); case INSTALLER_NAME: return atomicFormula.isMatch(appInstallMetadata.getInstallerName()); case INSTALLER_CERTIFICATE: return atomicFormula.isMatch(appInstallMetadata.getInstallerCertificate()); case VERSION_CODE: return atomicFormula.isMatch(appInstallMetadata.getVersionCode()); case PRE_INSTALLED: return atomicFormula.isMatch(appInstallMetadata.isPreInstalled()); break; case FORCE_ALLOW: return IntegrityCheckResult.allow(rule); default: Slog.i(TAG, String.format("Returned no match for unknown key %s", atomicFormula.getKey())); return false; Slog.e(TAG, "Matched an unknown effect rule: " + rule); return IntegrityCheckResult.allow(); } } else if (formula instanceof OpenFormula) { OpenFormula openFormula = (OpenFormula) formula; // A rule is in disjunctive normal form, so there are no OR connectors. switch (openFormula.getConnector()) { case NOT: // NOT connector has only 1 formula attached. return !isMatch(openFormula.getFormulas().get(0), appInstallMetadata); case AND: return openFormula.getFormulas().stream().allMatch( subFormula -> isMatch(subFormula, appInstallMetadata)); default: Slog.i(TAG, String.format("Returned no match for unknown connector %s", openFormula.getConnector())); return false; } } return false; return denied ? IntegrityCheckResult.deny(denyRule) : IntegrityCheckResult.allow(); } private static boolean isConjunctionOfFormulas(Formula formula) { Loading @@ -111,7 +90,7 @@ final class RuleEvaluator { return true; } OpenFormula openFormula = (OpenFormula) formula; return openFormula.getConnector() == OpenFormula.Connector.AND return openFormula.getConnector() == OpenFormula.AND && openFormula.getFormulas().stream().allMatch(RuleEvaluator::isAtomicFormula); } Loading @@ -120,7 +99,7 @@ final class RuleEvaluator { return true; } OpenFormula openFormula = (OpenFormula) formula; return openFormula.getConnector() == OpenFormula.Connector.NOT return openFormula.getConnector() == OpenFormula.NOT && openFormula.getFormulas().get(0) instanceof AtomicFormula; } } services/core/java/com/android/server/integrity/model/AppInstallMetadata.java +26 −13 Original line number Diff line number Diff line Loading @@ -19,7 +19,11 @@ package com.android.server.integrity.model; 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. Loading @@ -28,7 +32,11 @@ import android.annotation.Nullable; * 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. Loading @@ -48,10 +56,12 @@ public final class AppInstallMetadata { this.mIsPreInstalled = builder.mIsPreInstalled; } @NonNull public String getPackageName() { return mPackageName; } @NonNull public String getAppCertificate() { return mAppCertificate; } Loading @@ -66,23 +76,17 @@ public final class AppInstallMetadata { return mInstallerCertificate; } /** * @see AppInstallMetadata.Builder#setVersionCode(int) */ /** @see AppInstallMetadata.Builder#setVersionCode(int) */ public int getVersionCode() { return mVersionCode; } /** * @see AppInstallMetadata.Builder#setIsPreInstalled(boolean) */ /** @see AppInstallMetadata.Builder#setIsPreInstalled(boolean) */ public boolean isPreInstalled() { return mIsPreInstalled; } /** * Builder class for constructing {@link AppInstallMetadata} objects. */ /** Builder class for constructing {@link AppInstallMetadata} objects. */ public static final class Builder { private String mPackageName; private String mAppCertificate; Loading @@ -96,7 +100,8 @@ public final class AppInstallMetadata { * * @see AppInstallMetadata#getPackageName() */ public Builder setPackageName(String packageName) { @NonNull public Builder setPackageName(@NonNull String packageName) { this.mPackageName = checkNotNull(packageName); return this; } Loading @@ -109,7 +114,8 @@ public final class AppInstallMetadata { * * @see AppInstallMetadata#getAppCertificate() */ public Builder setAppCertificate(String appCertificate) { @NonNull public Builder setAppCertificate(@NonNull String appCertificate) { this.mAppCertificate = checkNotNull(appCertificate); return this; } Loading @@ -119,7 +125,8 @@ public final class AppInstallMetadata { * * @see AppInstallMetadata#getInstallerName() */ public Builder setInstallerName(String installerName) { @NonNull public Builder setInstallerName(@NonNull String installerName) { this.mInstallerName = checkNotNull(installerName); return this; } Loading @@ -132,7 +139,8 @@ public final class AppInstallMetadata { * * @see AppInstallMetadata#getInstallerCertificate() */ public Builder setInstallerCertificate(String installerCertificate) { @NonNull public Builder setInstallerCertificate(@NonNull String installerCertificate) { this.mInstallerCertificate = checkNotNull(installerCertificate); return this; } Loading @@ -142,6 +150,7 @@ public final class AppInstallMetadata { * * @see AppInstallMetadata#getVersionCode() */ @NonNull public Builder setVersionCode(int versionCode) { this.mVersionCode = versionCode; return this; Loading @@ -152,6 +161,7 @@ public final class AppInstallMetadata { * * @see AppInstallMetadata#isPreInstalled() */ @NonNull public Builder setIsPreInstalled(boolean isPreInstalled) { this.mIsPreInstalled = isPreInstalled; return this; Loading @@ -159,7 +169,10 @@ public final class AppInstallMetadata { /** * Build {@link AppInstallMetadata}. * * @throws IllegalArgumentException if package name or app certificate is null */ @NonNull public AppInstallMetadata build() { checkArgument(mPackageName != null); checkArgument(mAppCertificate != null); Loading services/core/java/com/android/server/integrity/model/AtomicFormula.java +348 −164 File changed.Preview size limit exceeded, changes collapsed. Show changes services/core/java/com/android/server/integrity/model/Formula.java +76 −1 Original line number Diff line number Diff line Loading @@ -16,9 +16,84 @@ package com.android.server.integrity.model; import android.annotation.NonNull; import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; import com.android.internal.annotations.VisibleForTesting; import com.android.server.integrity.model.AtomicFormula.BooleanAtomicFormula; import com.android.server.integrity.model.AtomicFormula.IntAtomicFormula; import com.android.server.integrity.model.AtomicFormula.StringAtomicFormula; /** * Represents a rule logic/content. * * @hide */ @SystemApi @VisibleForTesting public interface Formula { int OPEN_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); /** * 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 */ public abstract class Formula { static void writeToParcel(@NonNull Formula formula, @NonNull Parcel dest, int flags) { if (formula instanceof OpenFormula) { dest.writeInt(OPEN_FORMULA_TAG); ((OpenFormula) formula).writeToParcel(dest, flags); } else if (formula instanceof StringAtomicFormula) { dest.writeInt(STRING_ATOMIC_FORMULA_TAG); ((StringAtomicFormula) formula).writeToParcel(dest, flags); } else if (formula instanceof IntAtomicFormula) { dest.writeInt(INT_ATOMIC_FORMULA_TAG); ((IntAtomicFormula) formula).writeToParcel(dest, flags); } else if (formula instanceof BooleanAtomicFormula) { dest.writeInt(BOOLEAN_ATOMIC_FORMULA_TAG); ((BooleanAtomicFormula) formula).writeToParcel(dest, flags); } else { throw new IllegalArgumentException("Unrecognized class " + formula.getClass()); } } /** * 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 OPEN_FORMULA_TAG: return OpenFormula.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); } } } Loading
services/core/java/com/android/server/integrity/engine/RuleEvaluationEngine.java +2 −16 Original line number Diff line number Diff line Loading @@ -16,8 +16,6 @@ package com.android.server.integrity.engine; import android.util.Slog; import com.android.server.integrity.model.AppInstallMetadata; import com.android.server.integrity.model.IntegrityCheckResult; import com.android.server.integrity.model.Rule; Loading Loading @@ -53,23 +51,11 @@ public final class RuleEvaluationEngine { * * @param appInstallMetadata Metadata of the app to be installed, and to evaluate the rules * against. * @return A rule matching the metadata. If there are multiple matching rules, returns any. If * no rules are matching, returns {@link Rule#EMPTY}. * @return result of the integrity check */ public IntegrityCheckResult evaluate(AppInstallMetadata appInstallMetadata) { List<Rule> rules = loadRules(appInstallMetadata); Rule matchedRule = RuleEvaluator.evaluateRules(rules, appInstallMetadata); if (matchedRule == Rule.EMPTY) { return IntegrityCheckResult.allow(); } else { switch (matchedRule.getEffect()) { case DENY: return IntegrityCheckResult.deny(matchedRule); default: Slog.e(TAG, "Matched a non-DENY rule: " + matchedRule); return IntegrityCheckResult.allow(); } } return RuleEvaluator.evaluateRules(rules, appInstallMetadata); } private List<Rule> loadRules(AppInstallMetadata appInstallMetadata) { Loading
services/core/java/com/android/server/integrity/engine/RuleEvaluator.java +33 −54 Original line number Diff line number Diff line Loading @@ -16,14 +16,20 @@ package com.android.server.integrity.engine; import static com.android.server.integrity.model.Rule.DENY; import static com.android.server.integrity.model.Rule.FORCE_ALLOW; import android.annotation.NonNull; import android.util.Slog; import com.android.server.integrity.model.AppInstallMetadata; import com.android.server.integrity.model.AtomicFormula; import com.android.server.integrity.model.Formula; import com.android.server.integrity.model.IntegrityCheckResult; import com.android.server.integrity.model.OpenFormula; import com.android.server.integrity.model.Rule; import java.util.ArrayList; import java.util.List; /** Loading @@ -43,64 +49,37 @@ final class RuleEvaluator { * @param rules The list of rules to evaluate. * @param appInstallMetadata Metadata of the app to be installed, and to evaluate the rules * against. * @return A rule matching the metadata. If there are multiple matching rules, returns any. If * no rules are matching, returns {@link Rule#EMPTY}. * @return result of the integrity check */ static Rule evaluateRules(List<Rule> rules, AppInstallMetadata appInstallMetadata) { @NonNull static IntegrityCheckResult evaluateRules( List<Rule> rules, AppInstallMetadata appInstallMetadata) { List<Rule> matchedRules = new ArrayList<>(); for (Rule rule : rules) { if (isConjunctionOfFormulas(rule.getFormula()) && isMatch(rule, appInstallMetadata)) { return rule; } if (isConjunctionOfFormulas(rule.getFormula()) && rule.getFormula().isSatisfied(appInstallMetadata)) { matchedRules.add(rule); } return Rule.EMPTY; } /** * Match a rule against app install metadata. */ private static boolean isMatch(Rule rule, AppInstallMetadata appInstallMetadata) { return isMatch(rule.getFormula(), appInstallMetadata); boolean denied = false; Rule denyRule = null; for (Rule rule : matchedRules) { switch (rule.getEffect()) { case DENY: if (!denied) { denied = true; denyRule = rule; } private static boolean isMatch(Formula formula, AppInstallMetadata appInstallMetadata) { if (formula instanceof AtomicFormula) { AtomicFormula atomicFormula = (AtomicFormula) formula; switch (atomicFormula.getKey()) { case PACKAGE_NAME: return atomicFormula.isMatch(appInstallMetadata.getPackageName()); case APP_CERTIFICATE: return atomicFormula.isMatch(appInstallMetadata.getAppCertificate()); case INSTALLER_NAME: return atomicFormula.isMatch(appInstallMetadata.getInstallerName()); case INSTALLER_CERTIFICATE: return atomicFormula.isMatch(appInstallMetadata.getInstallerCertificate()); case VERSION_CODE: return atomicFormula.isMatch(appInstallMetadata.getVersionCode()); case PRE_INSTALLED: return atomicFormula.isMatch(appInstallMetadata.isPreInstalled()); break; case FORCE_ALLOW: return IntegrityCheckResult.allow(rule); default: Slog.i(TAG, String.format("Returned no match for unknown key %s", atomicFormula.getKey())); return false; Slog.e(TAG, "Matched an unknown effect rule: " + rule); return IntegrityCheckResult.allow(); } } else if (formula instanceof OpenFormula) { OpenFormula openFormula = (OpenFormula) formula; // A rule is in disjunctive normal form, so there are no OR connectors. switch (openFormula.getConnector()) { case NOT: // NOT connector has only 1 formula attached. return !isMatch(openFormula.getFormulas().get(0), appInstallMetadata); case AND: return openFormula.getFormulas().stream().allMatch( subFormula -> isMatch(subFormula, appInstallMetadata)); default: Slog.i(TAG, String.format("Returned no match for unknown connector %s", openFormula.getConnector())); return false; } } return false; return denied ? IntegrityCheckResult.deny(denyRule) : IntegrityCheckResult.allow(); } private static boolean isConjunctionOfFormulas(Formula formula) { Loading @@ -111,7 +90,7 @@ final class RuleEvaluator { return true; } OpenFormula openFormula = (OpenFormula) formula; return openFormula.getConnector() == OpenFormula.Connector.AND return openFormula.getConnector() == OpenFormula.AND && openFormula.getFormulas().stream().allMatch(RuleEvaluator::isAtomicFormula); } Loading @@ -120,7 +99,7 @@ final class RuleEvaluator { return true; } OpenFormula openFormula = (OpenFormula) formula; return openFormula.getConnector() == OpenFormula.Connector.NOT return openFormula.getConnector() == OpenFormula.NOT && openFormula.getFormulas().get(0) instanceof AtomicFormula; } }
services/core/java/com/android/server/integrity/model/AppInstallMetadata.java +26 −13 Original line number Diff line number Diff line Loading @@ -19,7 +19,11 @@ package com.android.server.integrity.model; 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. Loading @@ -28,7 +32,11 @@ import android.annotation.Nullable; * 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. Loading @@ -48,10 +56,12 @@ public final class AppInstallMetadata { this.mIsPreInstalled = builder.mIsPreInstalled; } @NonNull public String getPackageName() { return mPackageName; } @NonNull public String getAppCertificate() { return mAppCertificate; } Loading @@ -66,23 +76,17 @@ public final class AppInstallMetadata { return mInstallerCertificate; } /** * @see AppInstallMetadata.Builder#setVersionCode(int) */ /** @see AppInstallMetadata.Builder#setVersionCode(int) */ public int getVersionCode() { return mVersionCode; } /** * @see AppInstallMetadata.Builder#setIsPreInstalled(boolean) */ /** @see AppInstallMetadata.Builder#setIsPreInstalled(boolean) */ public boolean isPreInstalled() { return mIsPreInstalled; } /** * Builder class for constructing {@link AppInstallMetadata} objects. */ /** Builder class for constructing {@link AppInstallMetadata} objects. */ public static final class Builder { private String mPackageName; private String mAppCertificate; Loading @@ -96,7 +100,8 @@ public final class AppInstallMetadata { * * @see AppInstallMetadata#getPackageName() */ public Builder setPackageName(String packageName) { @NonNull public Builder setPackageName(@NonNull String packageName) { this.mPackageName = checkNotNull(packageName); return this; } Loading @@ -109,7 +114,8 @@ public final class AppInstallMetadata { * * @see AppInstallMetadata#getAppCertificate() */ public Builder setAppCertificate(String appCertificate) { @NonNull public Builder setAppCertificate(@NonNull String appCertificate) { this.mAppCertificate = checkNotNull(appCertificate); return this; } Loading @@ -119,7 +125,8 @@ public final class AppInstallMetadata { * * @see AppInstallMetadata#getInstallerName() */ public Builder setInstallerName(String installerName) { @NonNull public Builder setInstallerName(@NonNull String installerName) { this.mInstallerName = checkNotNull(installerName); return this; } Loading @@ -132,7 +139,8 @@ public final class AppInstallMetadata { * * @see AppInstallMetadata#getInstallerCertificate() */ public Builder setInstallerCertificate(String installerCertificate) { @NonNull public Builder setInstallerCertificate(@NonNull String installerCertificate) { this.mInstallerCertificate = checkNotNull(installerCertificate); return this; } Loading @@ -142,6 +150,7 @@ public final class AppInstallMetadata { * * @see AppInstallMetadata#getVersionCode() */ @NonNull public Builder setVersionCode(int versionCode) { this.mVersionCode = versionCode; return this; Loading @@ -152,6 +161,7 @@ public final class AppInstallMetadata { * * @see AppInstallMetadata#isPreInstalled() */ @NonNull public Builder setIsPreInstalled(boolean isPreInstalled) { this.mIsPreInstalled = isPreInstalled; return this; Loading @@ -159,7 +169,10 @@ public final class AppInstallMetadata { /** * Build {@link AppInstallMetadata}. * * @throws IllegalArgumentException if package name or app certificate is null */ @NonNull public AppInstallMetadata build() { checkArgument(mPackageName != null); checkArgument(mAppCertificate != null); Loading
services/core/java/com/android/server/integrity/model/AtomicFormula.java +348 −164 File changed.Preview size limit exceeded, changes collapsed. Show changes
services/core/java/com/android/server/integrity/model/Formula.java +76 −1 Original line number Diff line number Diff line Loading @@ -16,9 +16,84 @@ package com.android.server.integrity.model; import android.annotation.NonNull; import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; import com.android.internal.annotations.VisibleForTesting; import com.android.server.integrity.model.AtomicFormula.BooleanAtomicFormula; import com.android.server.integrity.model.AtomicFormula.IntAtomicFormula; import com.android.server.integrity.model.AtomicFormula.StringAtomicFormula; /** * Represents a rule logic/content. * * @hide */ @SystemApi @VisibleForTesting public interface Formula { int OPEN_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); /** * 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 */ public abstract class Formula { static void writeToParcel(@NonNull Formula formula, @NonNull Parcel dest, int flags) { if (formula instanceof OpenFormula) { dest.writeInt(OPEN_FORMULA_TAG); ((OpenFormula) formula).writeToParcel(dest, flags); } else if (formula instanceof StringAtomicFormula) { dest.writeInt(STRING_ATOMIC_FORMULA_TAG); ((StringAtomicFormula) formula).writeToParcel(dest, flags); } else if (formula instanceof IntAtomicFormula) { dest.writeInt(INT_ATOMIC_FORMULA_TAG); ((IntAtomicFormula) formula).writeToParcel(dest, flags); } else if (formula instanceof BooleanAtomicFormula) { dest.writeInt(BOOLEAN_ATOMIC_FORMULA_TAG); ((BooleanAtomicFormula) formula).writeToParcel(dest, flags); } else { throw new IllegalArgumentException("Unrecognized class " + formula.getClass()); } } /** * 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 OPEN_FORMULA_TAG: return OpenFormula.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); } } }