1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.content.integrity; 18 19 import static com.android.internal.util.Preconditions.checkArgument; 20 21 import android.annotation.IntDef; 22 import android.annotation.NonNull; 23 import android.os.Parcel; 24 import android.os.Parcelable; 25 26 import com.android.internal.annotations.VisibleForTesting; 27 28 import java.lang.annotation.Retention; 29 import java.lang.annotation.RetentionPolicy; 30 import java.util.ArrayList; 31 import java.util.Collections; 32 import java.util.List; 33 import java.util.Objects; 34 35 /** 36 * Represents a compound formula formed by joining other simple and complex formulas with boolean 37 * connectors. 38 * 39 * <p>Instances of this class are immutable. 40 * 41 * @hide 42 */ 43 @VisibleForTesting 44 public final class CompoundFormula extends IntegrityFormula implements Parcelable { 45 46 /** @hide */ 47 @IntDef(value = {AND, OR, NOT}) 48 @Retention(RetentionPolicy.SOURCE) 49 public @interface Connector {} 50 51 /** Boolean AND operator. */ 52 public static final int AND = 0; 53 54 /** Boolean OR operator. */ 55 public static final int OR = 1; 56 57 /** Boolean NOT operator. */ 58 public static final int NOT = 2; 59 60 private final @Connector int mConnector; 61 private final @NonNull List<IntegrityFormula> mFormulas; 62 63 @NonNull 64 public static final Creator<CompoundFormula> CREATOR = 65 new Creator<CompoundFormula>() { 66 @Override 67 public CompoundFormula createFromParcel(Parcel in) { 68 return new CompoundFormula(in); 69 } 70 71 @Override 72 public CompoundFormula[] newArray(int size) { 73 return new CompoundFormula[size]; 74 } 75 }; 76 77 /** 78 * Create a new formula from operator and operands. 79 * 80 * @throws IllegalArgumentException if the number of operands is not matching the requirements 81 * for that operator (at least 2 for {@link #AND} and {@link 82 * #OR}, 1 for {@link #NOT}). 83 */ CompoundFormula(@onnector int connector, List<IntegrityFormula> formulas)84 public CompoundFormula(@Connector int connector, List<IntegrityFormula> formulas) { 85 checkArgument( 86 isValidConnector(connector), String.format("Unknown connector: %d", connector)); 87 validateFormulas(connector, formulas); 88 this.mConnector = connector; 89 this.mFormulas = Collections.unmodifiableList(formulas); 90 } 91 CompoundFormula(Parcel in)92 CompoundFormula(Parcel in) { 93 mConnector = in.readInt(); 94 int length = in.readInt(); 95 checkArgument(length >= 0, "Must have non-negative length. Got " + length); 96 mFormulas = new ArrayList<>(length); 97 for (int i = 0; i < length; i++) { 98 mFormulas.add(IntegrityFormula.readFromParcel(in)); 99 } 100 validateFormulas(mConnector, mFormulas); 101 } 102 getConnector()103 public @Connector int getConnector() { 104 return mConnector; 105 } 106 107 @NonNull getFormulas()108 public List<IntegrityFormula> getFormulas() { 109 return mFormulas; 110 } 111 112 @Override getTag()113 public int getTag() { 114 return IntegrityFormula.COMPOUND_FORMULA_TAG; 115 } 116 117 @Override matches(AppInstallMetadata appInstallMetadata)118 public boolean matches(AppInstallMetadata appInstallMetadata) { 119 switch (getConnector()) { 120 case NOT: 121 return !getFormulas().get(0).matches(appInstallMetadata); 122 case AND: 123 return getFormulas().stream() 124 .allMatch(formula -> formula.matches(appInstallMetadata)); 125 case OR: 126 return getFormulas().stream() 127 .anyMatch(formula -> formula.matches(appInstallMetadata)); 128 default: 129 throw new IllegalArgumentException("Unknown connector " + getConnector()); 130 } 131 } 132 133 @Override isAppCertificateFormula()134 public boolean isAppCertificateFormula() { 135 return getFormulas().stream().anyMatch(formula -> formula.isAppCertificateFormula()); 136 } 137 138 @Override isInstallerFormula()139 public boolean isInstallerFormula() { 140 return getFormulas().stream().anyMatch(formula -> formula.isInstallerFormula()); 141 } 142 143 @Override toString()144 public String toString() { 145 StringBuilder sb = new StringBuilder(); 146 if (mFormulas.size() == 1) { 147 sb.append(String.format("%s ", connectorToString(mConnector))); 148 sb.append(mFormulas.get(0).toString()); 149 } else { 150 for (int i = 0; i < mFormulas.size(); i++) { 151 if (i > 0) { 152 sb.append(String.format(" %s ", connectorToString(mConnector))); 153 } 154 sb.append(mFormulas.get(i).toString()); 155 } 156 } 157 return sb.toString(); 158 } 159 160 @Override equals(Object o)161 public boolean equals(Object o) { 162 if (this == o) { 163 return true; 164 } 165 if (o == null || getClass() != o.getClass()) { 166 return false; 167 } 168 CompoundFormula that = (CompoundFormula) o; 169 return mConnector == that.mConnector && mFormulas.equals(that.mFormulas); 170 } 171 172 @Override hashCode()173 public int hashCode() { 174 return Objects.hash(mConnector, mFormulas); 175 } 176 177 @Override describeContents()178 public int describeContents() { 179 return 0; 180 } 181 182 @Override writeToParcel(@onNull Parcel dest, int flags)183 public void writeToParcel(@NonNull Parcel dest, int flags) { 184 dest.writeInt(mConnector); 185 dest.writeInt(mFormulas.size()); 186 for (IntegrityFormula formula : mFormulas) { 187 IntegrityFormula.writeToParcel(formula, dest, flags); 188 } 189 } 190 validateFormulas( @onnector int connector, List<IntegrityFormula> formulas)191 private static void validateFormulas( 192 @Connector int connector, List<IntegrityFormula> formulas) { 193 switch (connector) { 194 case AND: 195 case OR: 196 checkArgument( 197 formulas.size() >= 2, 198 String.format( 199 "Connector %s must have at least 2 formulas", 200 connectorToString(connector))); 201 break; 202 case NOT: 203 checkArgument( 204 formulas.size() == 1, 205 String.format( 206 "Connector %s must have 1 formula only", 207 connectorToString(connector))); 208 break; 209 } 210 } 211 connectorToString(int connector)212 private static String connectorToString(int connector) { 213 switch (connector) { 214 case AND: 215 return "AND"; 216 case OR: 217 return "OR"; 218 case NOT: 219 return "NOT"; 220 default: 221 throw new IllegalArgumentException("Unknown connector " + connector); 222 } 223 } 224 isValidConnector(int connector)225 private static boolean isValidConnector(int connector) { 226 return connector == AND || connector == OR || connector == NOT; 227 } 228 } 229