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