1 /* 2 * Copyright (C) 2022 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 com.android.server.devicepolicy; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.app.admin.PolicyValue; 22 import android.util.IndentingPrintWriter; 23 24 import com.android.internal.util.XmlUtils; 25 import com.android.modules.utils.TypedXmlPullParser; 26 import com.android.modules.utils.TypedXmlSerializer; 27 import com.android.server.utils.Slogf; 28 29 import org.xmlpull.v1.XmlPullParserException; 30 31 import java.io.IOException; 32 import java.util.LinkedHashMap; 33 import java.util.Objects; 34 35 /** 36 * Class containing all values set for a certain policy by different admins. 37 */ 38 final class PolicyState<V> { 39 40 private static final String TAG = "PolicyState"; 41 private static final String TAG_ADMIN_POLICY_ENTRY = "admin-policy-entry"; 42 43 private static final String TAG_POLICY_DEFINITION_ENTRY = "policy-definition-entry"; 44 private static final String TAG_RESOLVED_VALUE_ENTRY = "resolved-value-entry"; 45 private static final String TAG_ENFORCING_ADMIN_ENTRY = "enforcing-admin-entry"; 46 private static final String TAG_POLICY_VALUE_ENTRY = "policy-value-entry"; 47 private final PolicyDefinition<V> mPolicyDefinition; 48 private final LinkedHashMap<EnforcingAdmin, PolicyValue<V>> mPoliciesSetByAdmins = 49 new LinkedHashMap<>(); 50 private PolicyValue<V> mCurrentResolvedPolicy; 51 PolicyState(@onNull PolicyDefinition<V> policyDefinition)52 PolicyState(@NonNull PolicyDefinition<V> policyDefinition) { 53 mPolicyDefinition = Objects.requireNonNull(policyDefinition); 54 } 55 PolicyState( @onNull PolicyDefinition<V> policyDefinition, @NonNull LinkedHashMap<EnforcingAdmin, PolicyValue<V>> policiesSetByAdmins, PolicyValue<V> currentEnforcedPolicy)56 private PolicyState( 57 @NonNull PolicyDefinition<V> policyDefinition, 58 @NonNull LinkedHashMap<EnforcingAdmin, PolicyValue<V>> policiesSetByAdmins, 59 PolicyValue<V> currentEnforcedPolicy) { 60 Objects.requireNonNull(policyDefinition); 61 Objects.requireNonNull(policiesSetByAdmins); 62 63 mPolicyDefinition = policyDefinition; 64 mPoliciesSetByAdmins.putAll(policiesSetByAdmins); 65 mCurrentResolvedPolicy = currentEnforcedPolicy; 66 } 67 68 /** 69 * Returns {@code true} if the resolved policy has changed, {@code false} otherwise. 70 */ addPolicy(@onNull EnforcingAdmin admin, @Nullable PolicyValue<V> policy)71 boolean addPolicy(@NonNull EnforcingAdmin admin, @Nullable PolicyValue<V> policy) { 72 Objects.requireNonNull(admin); 73 74 //LinkedHashMap doesn't update the insertion order of existing keys, removing the existing 75 // key will cause it to update. 76 mPoliciesSetByAdmins.remove(admin); 77 mPoliciesSetByAdmins.put(admin, policy); 78 79 return resolvePolicy(); 80 } 81 82 /** 83 * Takes into account global policies set by the admin when resolving the policy, this is only 84 * relevant to local policies that can be applied globally as well. 85 * 86 * <p> Note that local policies set by an admin takes precedence over global policies set by the 87 * same admin. 88 * 89 * Returns {@code true} if the resolved policy has changed, {@code false} otherwise. 90 */ addPolicy( @onNull EnforcingAdmin admin, @Nullable PolicyValue<V> policy, LinkedHashMap<EnforcingAdmin, PolicyValue<V>> globalPoliciesSetByAdmins)91 boolean addPolicy( 92 @NonNull EnforcingAdmin admin, @Nullable PolicyValue<V> policy, 93 LinkedHashMap<EnforcingAdmin, PolicyValue<V>> globalPoliciesSetByAdmins) { 94 mPoliciesSetByAdmins.put(Objects.requireNonNull(admin), policy); 95 96 return resolvePolicy(globalPoliciesSetByAdmins); 97 } 98 99 /** 100 * Returns {@code true} if the resolved policy has changed, {@code false} otherwise. 101 */ removePolicy(@onNull EnforcingAdmin admin)102 boolean removePolicy(@NonNull EnforcingAdmin admin) { 103 Objects.requireNonNull(admin); 104 105 if (mPoliciesSetByAdmins.remove(admin) == null) { 106 return false; 107 } 108 109 return resolvePolicy(); 110 } 111 112 /** 113 * Takes into account global policies set by the admin when resolving the policy, this is only 114 * relevant to local policies that can be applied globally as well. 115 * 116 * <p> Note that local policies set by an admin takes precedence over global policies set by the 117 * same admin. 118 * 119 * Returns {@code true} if the resolved policy has changed, {@code false} otherwise. 120 */ removePolicy( @onNull EnforcingAdmin admin, LinkedHashMap<EnforcingAdmin, PolicyValue<V>> globalPoliciesSetByAdmins)121 boolean removePolicy( 122 @NonNull EnforcingAdmin admin, 123 LinkedHashMap<EnforcingAdmin, PolicyValue<V>> globalPoliciesSetByAdmins) { 124 Objects.requireNonNull(admin); 125 126 if (mPoliciesSetByAdmins.remove(admin) == null) { 127 return false; 128 } 129 130 return resolvePolicy(globalPoliciesSetByAdmins); 131 } 132 133 /** 134 * Takes into account global policies set by the admin when resolving the policy, this is only 135 * relevant to local policies that can be applied globally as well. 136 * 137 * <p> Note that local policies set by an admin takes precedence over global policies set by the 138 * same admin. 139 * 140 * Returns {@code true} if the resolved policy has changed, {@code false} otherwise. 141 */ resolvePolicy(LinkedHashMap<EnforcingAdmin, PolicyValue<V>> globalPoliciesSetByAdmins)142 boolean resolvePolicy(LinkedHashMap<EnforcingAdmin, PolicyValue<V>> globalPoliciesSetByAdmins) { 143 //Non coexistable policies don't need resolving 144 if (mPolicyDefinition.isNonCoexistablePolicy()) { 145 return false; 146 } 147 // Add global policies first then override with local policies for the same admin. 148 LinkedHashMap<EnforcingAdmin, PolicyValue<V>> mergedPolicies = 149 new LinkedHashMap<>(globalPoliciesSetByAdmins); 150 mergedPolicies.putAll(mPoliciesSetByAdmins); 151 152 PolicyValue<V> resolvedPolicy = mPolicyDefinition.resolvePolicy(mergedPolicies); 153 boolean policyChanged = !Objects.equals(resolvedPolicy, mCurrentResolvedPolicy); 154 mCurrentResolvedPolicy = resolvedPolicy; 155 156 return policyChanged; 157 } 158 159 @NonNull getPoliciesSetByAdmins()160 LinkedHashMap<EnforcingAdmin, PolicyValue<V>> getPoliciesSetByAdmins() { 161 return new LinkedHashMap<>(mPoliciesSetByAdmins); 162 } 163 resolvePolicy()164 private boolean resolvePolicy() { 165 //Non coexistable policies don't need resolving 166 if (mPolicyDefinition.isNonCoexistablePolicy()) { 167 return false; 168 } 169 PolicyValue<V> resolvedPolicy = mPolicyDefinition.resolvePolicy(mPoliciesSetByAdmins); 170 boolean policyChanged = !Objects.equals(resolvedPolicy, mCurrentResolvedPolicy); 171 mCurrentResolvedPolicy = resolvedPolicy; 172 173 return policyChanged; 174 } 175 176 @Nullable getCurrentResolvedPolicy()177 PolicyValue<V> getCurrentResolvedPolicy() { 178 return mCurrentResolvedPolicy; 179 } 180 getParcelablePolicyState()181 android.app.admin.PolicyState<V> getParcelablePolicyState() { 182 LinkedHashMap<android.app.admin.EnforcingAdmin, PolicyValue<V>> adminPolicies = 183 new LinkedHashMap<>(); 184 for (EnforcingAdmin admin : mPoliciesSetByAdmins.keySet()) { 185 adminPolicies.put(admin.getParcelableAdmin(), mPoliciesSetByAdmins.get(admin)); 186 } 187 return new android.app.admin.PolicyState<>(adminPolicies, mCurrentResolvedPolicy, 188 mPolicyDefinition.getResolutionMechanism().getParcelableResolutionMechanism()); 189 } 190 191 @Override toString()192 public String toString() { 193 return "\nPolicyKey - " + mPolicyDefinition.getPolicyKey() 194 + "\nmPolicyDefinition= \n\t" + mPolicyDefinition 195 + "\nmPoliciesSetByAdmins= \n\t" + mPoliciesSetByAdmins 196 + ",\nmCurrentResolvedPolicy= \n\t" + mCurrentResolvedPolicy + " }"; 197 } 198 dump(IndentingPrintWriter pw)199 public void dump(IndentingPrintWriter pw) { 200 pw.println(mPolicyDefinition.getPolicyKey()); 201 pw.increaseIndent(); 202 203 pw.println("Per-admin Policy:"); 204 pw.increaseIndent(); 205 if (mPoliciesSetByAdmins.size() == 0) { 206 pw.println("null"); 207 } else { 208 for (EnforcingAdmin admin : mPoliciesSetByAdmins.keySet()) { 209 pw.println(admin); 210 pw.increaseIndent(); 211 pw.println(mPoliciesSetByAdmins.get(admin)); 212 pw.decreaseIndent(); 213 } 214 } 215 pw.decreaseIndent(); 216 217 pw.printf("Resolved Policy (%s):\n", 218 mPolicyDefinition.getResolutionMechanism().getClass().getSimpleName()); 219 pw.increaseIndent(); 220 pw.println(mCurrentResolvedPolicy); 221 pw.decreaseIndent(); 222 223 pw.decreaseIndent(); 224 } 225 saveToXml(TypedXmlSerializer serializer)226 void saveToXml(TypedXmlSerializer serializer) throws IOException { 227 serializer.startTag(/* namespace= */ null, TAG_POLICY_DEFINITION_ENTRY); 228 mPolicyDefinition.saveToXml(serializer); 229 serializer.endTag(/* namespace= */ null, TAG_POLICY_DEFINITION_ENTRY); 230 231 if (mCurrentResolvedPolicy != null) { 232 serializer.startTag(/* namespace= */ null, TAG_RESOLVED_VALUE_ENTRY); 233 mPolicyDefinition.savePolicyValueToXml( 234 serializer, mCurrentResolvedPolicy.getValue()); 235 serializer.endTag(/* namespace= */ null, TAG_RESOLVED_VALUE_ENTRY); 236 } 237 238 for (EnforcingAdmin admin : mPoliciesSetByAdmins.keySet()) { 239 serializer.startTag(/* namespace= */ null, TAG_ADMIN_POLICY_ENTRY); 240 241 if (mPoliciesSetByAdmins.get(admin) != null) { 242 serializer.startTag(/* namespace= */ null, TAG_POLICY_VALUE_ENTRY); 243 mPolicyDefinition.savePolicyValueToXml( 244 serializer, mPoliciesSetByAdmins.get(admin).getValue()); 245 serializer.endTag(/* namespace= */ null, TAG_POLICY_VALUE_ENTRY); 246 } 247 248 serializer.startTag(/* namespace= */ null, TAG_ENFORCING_ADMIN_ENTRY); 249 admin.saveToXml(serializer); 250 serializer.endTag(/* namespace= */ null, TAG_ENFORCING_ADMIN_ENTRY); 251 252 serializer.endTag(/* namespace= */ null, TAG_ADMIN_POLICY_ENTRY); 253 } 254 } 255 256 @Nullable readFromXml(TypedXmlPullParser parser)257 static <V> PolicyState<V> readFromXml(TypedXmlPullParser parser) 258 throws IOException, XmlPullParserException { 259 260 PolicyDefinition<V> policyDefinition = null; 261 262 PolicyValue<V> currentResolvedPolicy = null; 263 264 LinkedHashMap<EnforcingAdmin, PolicyValue<V>> policiesSetByAdmins = new LinkedHashMap<>(); 265 int outerDepth = parser.getDepth(); 266 while (XmlUtils.nextElementWithin(parser, outerDepth)) { 267 String tag = parser.getName(); 268 switch (tag) { 269 case TAG_ADMIN_POLICY_ENTRY: 270 PolicyValue<V> value = null; 271 EnforcingAdmin admin = null; 272 int adminPolicyDepth = parser.getDepth(); 273 while (XmlUtils.nextElementWithin(parser, adminPolicyDepth)) { 274 String adminPolicyTag = parser.getName(); 275 switch (adminPolicyTag) { 276 case TAG_ENFORCING_ADMIN_ENTRY: 277 admin = EnforcingAdmin.readFromXml(parser); 278 if (admin == null) { 279 Slogf.wtf(TAG, "Error Parsing TAG_ENFORCING_ADMIN_ENTRY, " 280 + "EnforcingAdmin is null"); 281 } 282 break; 283 case TAG_POLICY_VALUE_ENTRY: 284 value = policyDefinition.readPolicyValueFromXml(parser); 285 if (value == null) { 286 Slogf.wtf(TAG, "Error Parsing TAG_POLICY_VALUE_ENTRY, " 287 + "PolicyValue is null"); 288 } 289 break; 290 } 291 } 292 if (admin != null && value != null) { 293 policiesSetByAdmins.put(admin, value); 294 } else { 295 Slogf.wtf(TAG, "Error Parsing TAG_ADMIN_POLICY_ENTRY for " 296 + (policyDefinition == null ? "unknown policy" : "policy with " 297 + "definition " + policyDefinition) + ", EnforcingAdmin is: " 298 + (admin == null ? "null" : admin) + ", value is : " 299 + (value == null ? "null" : value)); 300 } 301 break; 302 case TAG_POLICY_DEFINITION_ENTRY: 303 policyDefinition = PolicyDefinition.readFromXml(parser); 304 if (policyDefinition == null) { 305 Slogf.wtf(TAG, "Error Parsing TAG_POLICY_DEFINITION_ENTRY, " 306 + "PolicyDefinition is null"); 307 } 308 break; 309 310 case TAG_RESOLVED_VALUE_ENTRY: 311 if (policyDefinition == null) { 312 Slogf.wtf(TAG, "Error Parsing TAG_RESOLVED_VALUE_ENTRY, " 313 + "policyDefinition is null"); 314 break; 315 } 316 currentResolvedPolicy = policyDefinition.readPolicyValueFromXml(parser); 317 if (currentResolvedPolicy == null) { 318 Slogf.wtf(TAG, "Error Parsing TAG_RESOLVED_VALUE_ENTRY for " 319 + (policyDefinition == null ? "unknown policy" : "policy with " 320 + "definition " + policyDefinition) + ", " 321 + "currentResolvedPolicy is null"); 322 } 323 break; 324 default: 325 Slogf.wtf(TAG, "Unknown tag: " + tag); 326 } 327 } 328 if (policyDefinition != null) { 329 return new PolicyState<V>(policyDefinition, policiesSetByAdmins, currentResolvedPolicy); 330 } else { 331 Slogf.wtf(TAG, "Error parsing policyState, policyDefinition is null"); 332 return null; 333 } 334 } 335 336 337 getPolicyDefinition()338 PolicyDefinition<V> getPolicyDefinition() { 339 return mPolicyDefinition; 340 } 341 } 342