1 /* 2 * Copyright (C) 2016 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.om; 18 19 import static com.android.server.om.OverlayManagerService.DEBUG; 20 import static com.android.server.om.OverlayManagerService.TAG; 21 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.content.om.OverlayConstraint; 25 import android.content.om.OverlayIdentifier; 26 import android.content.om.OverlayInfo; 27 import android.os.UserHandle; 28 import android.util.ArrayMap; 29 import android.util.ArraySet; 30 import android.util.IndentingPrintWriter; 31 import android.util.Pair; 32 import android.util.Slog; 33 import android.util.Xml; 34 35 import com.android.internal.annotations.VisibleForTesting; 36 import com.android.internal.util.CollectionUtils; 37 import com.android.internal.util.XmlUtils; 38 import com.android.modules.utils.TypedXmlPullParser; 39 import com.android.modules.utils.TypedXmlSerializer; 40 41 import org.xmlpull.v1.XmlPullParserException; 42 43 import java.io.IOException; 44 import java.io.InputStream; 45 import java.io.OutputStream; 46 import java.io.PrintWriter; 47 import java.util.ArrayList; 48 import java.util.Collections; 49 import java.util.List; 50 import java.util.Objects; 51 import java.util.Set; 52 import java.util.function.Consumer; 53 import java.util.function.Predicate; 54 55 /** 56 * Data structure representing the current state of all overlay packages in the 57 * system. 58 * 59 * Modifications to the data are signaled by returning true from any state mutating method. 60 * 61 * @see OverlayManagerService 62 */ 63 final class OverlayManagerSettings { 64 /** 65 * All overlay data for all users and target packages is stored in this list. 66 * This keeps memory down, while increasing the cost of running queries or mutating the 67 * data. This is ok, since changing of overlays is very rare and has larger costs associated 68 * with it. 69 * 70 * The order of the items in the list is important, those with a lower index having a lower 71 * priority. 72 */ 73 private final ArrayList<SettingsItem> mItems = new ArrayList<>(); 74 75 @NonNull init(@onNull final OverlayIdentifier overlay, final int userId, @NonNull final String targetPackageName, @Nullable final String targetOverlayableName, @NonNull final String baseCodePath, boolean isMutable, boolean isEnabled, int priority, @Nullable String overlayCategory, boolean isFabricated)76 OverlayInfo init(@NonNull final OverlayIdentifier overlay, final int userId, 77 @NonNull final String targetPackageName, @Nullable final String targetOverlayableName, 78 @NonNull final String baseCodePath, boolean isMutable, boolean isEnabled, int priority, 79 @Nullable String overlayCategory, boolean isFabricated) { 80 remove(overlay, userId); 81 final SettingsItem item = new SettingsItem(overlay, userId, targetPackageName, 82 targetOverlayableName, baseCodePath, OverlayInfo.STATE_UNKNOWN, isEnabled, 83 isMutable, priority, overlayCategory, isFabricated, 84 Collections.emptyList() /* constraints */); 85 insert(item); 86 return item.getOverlayInfo(); 87 } 88 89 /** 90 * Returns true if the settings were modified, false if they remain the same. 91 */ remove(@onNull final OverlayIdentifier overlay, final int userId)92 boolean remove(@NonNull final OverlayIdentifier overlay, final int userId) { 93 final int idx = select(overlay, userId); 94 if (idx < 0) { 95 return false; 96 } 97 mItems.remove(idx); 98 return true; 99 } 100 getOverlayInfo(@onNull final OverlayIdentifier overlay, final int userId)101 @NonNull OverlayInfo getOverlayInfo(@NonNull final OverlayIdentifier overlay, final int userId) 102 throws BadKeyException { 103 final int idx = select(overlay, userId); 104 if (idx < 0) { 105 throw new BadKeyException(overlay, userId); 106 } 107 return mItems.get(idx).getOverlayInfo(); 108 } 109 110 @Nullable getNullableOverlayInfo(@onNull final OverlayIdentifier overlay, final int userId)111 OverlayInfo getNullableOverlayInfo(@NonNull final OverlayIdentifier overlay, final int userId) { 112 final int idx = select(overlay, userId); 113 if (idx < 0) { 114 return null; 115 } 116 return mItems.get(idx).getOverlayInfo(); 117 } 118 119 /** 120 * Returns true if the settings were modified, false if they remain the same. 121 */ setBaseCodePath(@onNull final OverlayIdentifier overlay, final int userId, @NonNull final String path)122 boolean setBaseCodePath(@NonNull final OverlayIdentifier overlay, final int userId, 123 @NonNull final String path) throws BadKeyException { 124 final int idx = select(overlay, userId); 125 if (idx < 0) { 126 throw new BadKeyException(overlay, userId); 127 } 128 return mItems.get(idx).setBaseCodePath(path); 129 } 130 setCategory(@onNull final OverlayIdentifier overlay, final int userId, @Nullable String category)131 boolean setCategory(@NonNull final OverlayIdentifier overlay, final int userId, 132 @Nullable String category) throws BadKeyException { 133 final int idx = select(overlay, userId); 134 if (idx < 0) { 135 throw new BadKeyException(overlay, userId); 136 } 137 return mItems.get(idx).setCategory(category); 138 } 139 getEnabled(@onNull final OverlayIdentifier overlay, final int userId)140 boolean getEnabled(@NonNull final OverlayIdentifier overlay, final int userId) 141 throws BadKeyException { 142 final int idx = select(overlay, userId); 143 if (idx < 0) { 144 throw new BadKeyException(overlay, userId); 145 } 146 return mItems.get(idx).isEnabled(); 147 } 148 149 /** 150 * Returns true if the settings were modified, false if they remain the same. 151 */ setEnabled(@onNull final OverlayIdentifier overlay, final int userId, final boolean enable)152 boolean setEnabled(@NonNull final OverlayIdentifier overlay, final int userId, 153 final boolean enable) throws BadKeyException { 154 final int idx = select(overlay, userId); 155 if (idx < 0) { 156 throw new BadKeyException(overlay, userId); 157 } 158 return mItems.get(idx).setEnabled(enable); 159 } 160 setConstraints(@onNull final OverlayIdentifier overlay, final int userId, @NonNull final List<OverlayConstraint> constraints)161 boolean setConstraints(@NonNull final OverlayIdentifier overlay, final int userId, 162 @NonNull final List<OverlayConstraint> constraints) throws BadKeyException { 163 final int idx = select(overlay, userId); 164 if (idx < 0) { 165 throw new BadKeyException(overlay, userId); 166 } 167 return mItems.get(idx).setConstraints(constraints); 168 } 169 getState(@onNull final OverlayIdentifier overlay, final int userId)170 @OverlayInfo.State int getState(@NonNull final OverlayIdentifier overlay, final int userId) 171 throws BadKeyException { 172 final int idx = select(overlay, userId); 173 if (idx < 0) { 174 throw new BadKeyException(overlay, userId); 175 } 176 return mItems.get(idx).getState(); 177 } 178 179 /** 180 * Returns true if the settings were modified, false if they remain the same. 181 */ setState(@onNull final OverlayIdentifier overlay, final int userId, final @OverlayInfo.State int state)182 boolean setState(@NonNull final OverlayIdentifier overlay, final int userId, 183 final @OverlayInfo.State int state) throws BadKeyException { 184 final int idx = select(overlay, userId); 185 if (idx < 0) { 186 throw new BadKeyException(overlay, userId); 187 } 188 return mItems.get(idx).setState(state); 189 } 190 getOverlaysForTarget(@onNull final String targetPackageName, final int userId)191 List<OverlayInfo> getOverlaysForTarget(@NonNull final String targetPackageName, 192 final int userId) { 193 final List<SettingsItem> items = selectWhereTarget(targetPackageName, userId); 194 return CollectionUtils.map(items, SettingsItem::getOverlayInfo); 195 } 196 forEachMatching(int userId, String overlayName, String targetPackageName, @NonNull Consumer<OverlayInfo> consumer)197 void forEachMatching(int userId, String overlayName, String targetPackageName, 198 @NonNull Consumer<OverlayInfo> consumer) { 199 for (int i = 0, n = mItems.size(); i < n; i++) { 200 final SettingsItem item = mItems.get(i); 201 if (item.getUserId() != userId) { 202 continue; 203 } 204 if (overlayName != null && !item.mOverlay.getPackageName().equals(overlayName)) { 205 continue; 206 } 207 if (targetPackageName != null && !item.mTargetPackageName.equals(targetPackageName)) { 208 continue; 209 } 210 consumer.accept(item.getOverlayInfo()); 211 } 212 } 213 getOverlaysForUser(final int userId)214 ArrayMap<String, List<OverlayInfo>> getOverlaysForUser(final int userId) { 215 final List<SettingsItem> items = selectWhereUser(userId); 216 217 final ArrayMap<String, List<OverlayInfo>> targetInfos = new ArrayMap<>(); 218 for (int i = 0, n = items.size(); i < n; i++) { 219 final SettingsItem item = items.get(i); 220 targetInfos.computeIfAbsent(item.mTargetPackageName, (String) -> new ArrayList<>()) 221 .add(item.getOverlayInfo()); 222 } 223 return targetInfos; 224 } 225 getAllBaseCodePaths()226 Set<String> getAllBaseCodePaths() { 227 // Overlays installed for multiple users have the same code path, avoid duplicates with Set. 228 final Set<String> paths = new ArraySet<>(); 229 mItems.forEach(item -> paths.add(item.mBaseCodePath)); 230 return paths; 231 } 232 getAllIdentifiersAndBaseCodePaths()233 Set<Pair<OverlayIdentifier, String>> getAllIdentifiersAndBaseCodePaths() { 234 // Overlays installed for multiple users have the same code path, avoid duplicates with Set. 235 final Set<Pair<OverlayIdentifier, String>> set = new ArraySet<>(); 236 mItems.forEach(item -> set.add(new Pair<>(item.mOverlay, item.mBaseCodePath))); 237 return set; 238 } 239 240 @Nullable getIdentifierAndBaseCodePath(@onNull DumpState dumpState)241 Pair<OverlayIdentifier, String> getIdentifierAndBaseCodePath(@NonNull DumpState dumpState) { 242 if (dumpState.getPackageName() == null) { 243 return null; 244 } 245 OverlayIdentifier id = new OverlayIdentifier(dumpState.getPackageName(), 246 dumpState.getOverlayName()); 247 final int userId = dumpState.getUserId(); 248 for (int i = 0; i < mItems.size(); i++) { 249 final var item = mItems.get(i); 250 if (userId != UserHandle.USER_ALL && userId != item.mUserId) { 251 continue; 252 } 253 if (!id.equals(item.mOverlay)) { 254 continue; 255 } 256 // Overlays installed for multiple users have the same code path, return first found. 257 return new Pair<>(id, item.mBaseCodePath); 258 } 259 return null; 260 } 261 262 @NonNull removeIf(@onNull final Predicate<OverlayInfo> predicate, final int userId)263 List<OverlayInfo> removeIf(@NonNull final Predicate<OverlayInfo> predicate, final int userId) { 264 return removeIf(info -> (predicate.test(info) && info.userId == userId)); 265 } 266 267 @NonNull removeIf(final @NonNull Predicate<OverlayInfo> predicate)268 List<OverlayInfo> removeIf(final @NonNull Predicate<OverlayInfo> predicate) { 269 List<OverlayInfo> removed = null; 270 for (int i = mItems.size() - 1; i >= 0; i--) { 271 final OverlayInfo info = mItems.get(i).getOverlayInfo(); 272 if (predicate.test(info)) { 273 mItems.remove(i); 274 removed = CollectionUtils.add(removed, info); 275 } 276 } 277 return CollectionUtils.emptyIfNull(removed); 278 } 279 getUsers()280 int[] getUsers() { 281 return mItems.stream().mapToInt(SettingsItem::getUserId).distinct().toArray(); 282 } 283 284 /** 285 * Returns true if the settings were modified, false if they remain the same. 286 */ removeUser(final int userId)287 boolean removeUser(final int userId) { 288 return mItems.removeIf(item -> { 289 if (item.getUserId() == userId) { 290 if (DEBUG) { 291 Slog.d(TAG, "Removing overlay " + item.mOverlay + " for user " + userId 292 + " from settings because user was removed"); 293 } 294 return true; 295 } 296 return false; 297 }); 298 } 299 300 /** 301 * Reassigns the priority of an overlay maintaining the values of the overlays other settings. 302 */ 303 void setPriority(@NonNull final OverlayIdentifier overlay, final int userId, 304 final int priority) throws BadKeyException { 305 final int moveIdx = select(overlay, userId); 306 if (moveIdx < 0) { 307 throw new BadKeyException(overlay, userId); 308 } 309 310 final SettingsItem itemToMove = mItems.get(moveIdx); 311 mItems.remove(moveIdx); 312 itemToMove.setPriority(priority); 313 insert(itemToMove); 314 } 315 316 /** 317 * Returns true if the settings were modified, false if they remain the same. 318 */ 319 boolean setPriority(@NonNull final OverlayIdentifier overlay, 320 @NonNull final OverlayIdentifier newOverlay, final int userId) { 321 if (overlay.equals(newOverlay)) { 322 return false; 323 } 324 final int moveIdx = select(overlay, userId); 325 if (moveIdx < 0) { 326 return false; 327 } 328 329 final int parentIdx = select(newOverlay, userId); 330 if (parentIdx < 0) { 331 return false; 332 } 333 334 final SettingsItem itemToMove = mItems.get(moveIdx); 335 336 // Make sure both packages are targeting the same package. 337 if (!itemToMove.getTargetPackageName().equals( 338 mItems.get(parentIdx).getTargetPackageName())) { 339 return false; 340 } 341 342 mItems.remove(moveIdx); 343 final int newParentIdx = select(newOverlay, userId) + 1; 344 mItems.add(newParentIdx, itemToMove); 345 return moveIdx != newParentIdx; 346 } 347 348 /** 349 * Returns true if the settings were modified, false if they remain the same. 350 */ 351 boolean setLowestPriority(@NonNull final OverlayIdentifier overlay, final int userId) { 352 final int idx = select(overlay, userId); 353 if (idx <= 0) { 354 // If the item doesn't exist or is already the lowest, don't change anything. 355 return false; 356 } 357 358 final SettingsItem item = mItems.get(idx); 359 mItems.remove(item); 360 mItems.add(0, item); 361 return true; 362 } 363 364 /** 365 * Returns true if the settings were modified, false if they remain the same. 366 */ 367 boolean setHighestPriority(@NonNull final OverlayIdentifier overlay, final int userId) { 368 final int idx = select(overlay, userId); 369 370 // If the item doesn't exist or is already the highest, don't change anything. 371 if (idx < 0 || idx == mItems.size() - 1) { 372 return false; 373 } 374 375 final SettingsItem item = mItems.get(idx); 376 mItems.remove(idx); 377 mItems.add(item); 378 return true; 379 } 380 381 /** 382 * Inserts the item into the list of settings items. 383 */ 384 private void insert(@NonNull SettingsItem item) { 385 int i; 386 for (i = mItems.size() - 1; i >= 0; i--) { 387 SettingsItem parentItem = mItems.get(i); 388 if (parentItem.mPriority <= item.getPriority()) { 389 break; 390 } 391 } 392 mItems.add(i + 1, item); 393 } 394 395 void dump(@NonNull final PrintWriter p, @NonNull DumpState dumpState) { 396 final int userId = dumpState.getUserId(); 397 final String packageName = dumpState.getPackageName(); 398 final String overlayName = dumpState.getOverlayName(); 399 final String field = dumpState.getField(); 400 final var pw = new IndentingPrintWriter(p, " "); 401 402 for (int i = 0; i < mItems.size(); i++) { 403 final var item = mItems.get(i); 404 if (userId != UserHandle.USER_ALL && userId != item.mUserId) { 405 continue; 406 } 407 if (packageName != null && !packageName.equals(item.mOverlay.getPackageName())) { 408 continue; 409 } 410 if (overlayName != null && !overlayName.equals(item.mOverlay.getOverlayName())) { 411 continue; 412 } 413 414 if (field != null) { 415 dumpSettingsItemField(pw, item, field); 416 } else { 417 dumpSettingsItem(pw, item); 418 } 419 } 420 } 421 422 private void dumpSettingsItem(@NonNull final IndentingPrintWriter pw, 423 @NonNull final SettingsItem item) { 424 pw.println(item.mOverlay + ":" + item.getUserId() + " {"); 425 pw.increaseIndent(); 426 427 pw.println("mPackageName...........: " + item.mOverlay.getPackageName()); 428 pw.println("mOverlayName...........: " + item.mOverlay.getOverlayName()); 429 pw.println("mUserId................: " + item.getUserId()); 430 pw.println("mTargetPackageName.....: " + item.getTargetPackageName()); 431 pw.println("mTargetOverlayableName.: " + item.getTargetOverlayableName()); 432 pw.println("mBaseCodePath..........: " + item.getBaseCodePath()); 433 pw.println("mState.................: " + OverlayInfo.stateToString(item.getState())); 434 pw.println("mIsEnabled.............: " + item.isEnabled()); 435 pw.println("mIsMutable.............: " + item.isMutable()); 436 pw.println("mPriority..............: " + item.mPriority); 437 pw.println("mCategory..............: " + item.mCategory); 438 pw.println("mIsFabricated..........: " + item.mIsFabricated); 439 pw.println("mConstraints...........: " 440 + OverlayConstraint.constraintsToString(item.mConstraints)); 441 442 pw.decreaseIndent(); 443 pw.println("}"); 444 } 445 446 private void dumpSettingsItemField(@NonNull final IndentingPrintWriter pw, 447 @NonNull final SettingsItem item, @NonNull final String field) { 448 switch (field) { 449 case "packagename": 450 pw.println(item.mOverlay.getPackageName()); 451 break; 452 case "overlayname": 453 pw.println(item.mOverlay.getOverlayName()); 454 break; 455 case "userid": 456 pw.println(item.mUserId); 457 break; 458 case "targetpackagename": 459 pw.println(item.mTargetPackageName); 460 break; 461 case "targetoverlayablename": 462 pw.println(item.mTargetOverlayableName); 463 break; 464 case "basecodepath": 465 pw.println(item.mBaseCodePath); 466 break; 467 case "state": 468 pw.println(OverlayInfo.stateToString(item.mState)); 469 break; 470 case "isenabled": 471 pw.println(item.mIsEnabled); 472 break; 473 case "ismutable": 474 pw.println(item.mIsMutable); 475 break; 476 case "priority": 477 pw.println(item.mPriority); 478 break; 479 case "category": 480 pw.println(item.mCategory); 481 break; 482 } 483 } 484 485 void restore(@NonNull final InputStream is) throws IOException, XmlPullParserException { 486 Serializer.restore(mItems, is); 487 } 488 489 void persist(@NonNull final OutputStream os) throws IOException, XmlPullParserException { 490 Serializer.persist(mItems, os); 491 } 492 493 @VisibleForTesting 494 static final class Serializer { 495 private static final String TAG_OVERLAYS = "overlays"; 496 private static final String TAG_ITEM = "item"; 497 private static final String TAG_CONSTRAINT = "constraint"; 498 499 private static final String ATTR_BASE_CODE_PATH = "baseCodePath"; 500 private static final String ATTR_IS_ENABLED = "isEnabled"; 501 private static final String ATTR_PACKAGE_NAME = "packageName"; 502 private static final String ATTR_OVERLAY_NAME = "overlayName"; 503 private static final String ATTR_STATE = "state"; 504 private static final String ATTR_TARGET_PACKAGE_NAME = "targetPackageName"; 505 private static final String ATTR_TARGET_OVERLAYABLE_NAME = "targetOverlayableName"; 506 private static final String ATTR_IS_STATIC = "isStatic"; 507 private static final String ATTR_PRIORITY = "priority"; 508 private static final String ATTR_CATEGORY = "category"; 509 private static final String ATTR_USER_ID = "userId"; 510 private static final String ATTR_VERSION = "version"; 511 private static final String ATTR_IS_FABRICATED = "fabricated"; 512 513 private static final String ATTR_CONSTRAINT_TYPE = "type"; 514 private static final String ATTR_CONSTRAINT_VALUE = "value"; 515 516 @VisibleForTesting 517 static final int CURRENT_VERSION = 5; 518 519 public static void restore(@NonNull final ArrayList<SettingsItem> table, 520 @NonNull final InputStream is) throws IOException, XmlPullParserException { 521 table.clear(); 522 final TypedXmlPullParser parser = Xml.resolvePullParser(is); 523 XmlUtils.beginDocument(parser, TAG_OVERLAYS); 524 final int version = parser.getAttributeInt(null, ATTR_VERSION); 525 if (version != CURRENT_VERSION) { 526 upgrade(version); 527 } 528 529 final int depth = parser.getDepth(); 530 while (XmlUtils.nextElementWithin(parser, depth)) { 531 if (TAG_ITEM.equals(parser.getName())) { 532 final SettingsItem item = restoreRow(parser, depth + 1); 533 table.add(item); 534 } 535 } 536 } 537 538 private static void upgrade(int oldVersion) throws XmlPullParserException { 539 switch (oldVersion) { 540 case 0: 541 case 1: 542 case 2: 543 // Throw an exception which will cause the overlay file to be ignored 544 // and overwritten. 545 throw new XmlPullParserException("old version " + oldVersion + "; ignoring"); 546 case 3: 547 case 4: 548 // Upgrading from versions 3 and 4 is not a breaking change, so do not 549 // ignore the overlay file. 550 return; 551 default: 552 throw new XmlPullParserException("unrecognized version " + oldVersion); 553 } 554 } 555 556 private static SettingsItem restoreRow(@NonNull final TypedXmlPullParser parser, 557 final int depth) throws IOException, XmlPullParserException { 558 final OverlayIdentifier overlay = new OverlayIdentifier( 559 XmlUtils.readStringAttribute(parser, ATTR_PACKAGE_NAME), 560 XmlUtils.readStringAttribute(parser, ATTR_OVERLAY_NAME)); 561 final int userId = parser.getAttributeInt(null, ATTR_USER_ID); 562 final String targetPackageName = XmlUtils.readStringAttribute(parser, 563 ATTR_TARGET_PACKAGE_NAME); 564 final String targetOverlayableName = XmlUtils.readStringAttribute(parser, 565 ATTR_TARGET_OVERLAYABLE_NAME); 566 final String baseCodePath = XmlUtils.readStringAttribute(parser, ATTR_BASE_CODE_PATH); 567 final int state = parser.getAttributeInt(null, ATTR_STATE); 568 final boolean isEnabled = parser.getAttributeBoolean(null, ATTR_IS_ENABLED, false); 569 final boolean isStatic = parser.getAttributeBoolean(null, ATTR_IS_STATIC, false); 570 final int priority = parser.getAttributeInt(null, ATTR_PRIORITY); 571 final String category = XmlUtils.readStringAttribute(parser, ATTR_CATEGORY); 572 final boolean isFabricated = parser.getAttributeBoolean(null, ATTR_IS_FABRICATED, 573 false); 574 575 final List<OverlayConstraint> constraints = new ArrayList<>(); 576 while (XmlUtils.nextElementWithin(parser, depth)) { 577 if (TAG_CONSTRAINT.equals(parser.getName())) { 578 final OverlayConstraint constraint = new OverlayConstraint( 579 parser.getAttributeInt(null, ATTR_CONSTRAINT_TYPE), 580 parser.getAttributeInt(null, ATTR_CONSTRAINT_VALUE)); 581 constraints.add(constraint); 582 } 583 } 584 585 return new SettingsItem(overlay, userId, targetPackageName, targetOverlayableName, 586 baseCodePath, state, isEnabled, !isStatic, priority, category, isFabricated, 587 constraints); 588 } 589 590 public static void persist(@NonNull final ArrayList<SettingsItem> table, 591 @NonNull final OutputStream os) throws IOException { 592 final TypedXmlSerializer xml = Xml.resolveSerializer(os); 593 xml.startDocument(null, true); 594 xml.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); 595 xml.startTag(null, TAG_OVERLAYS); 596 xml.attributeInt(null, ATTR_VERSION, CURRENT_VERSION); 597 598 final int n = table.size(); 599 for (int i = 0; i < n; i++) { 600 final SettingsItem item = table.get(i); 601 persistRow(xml, item); 602 } 603 xml.endTag(null, TAG_OVERLAYS); 604 xml.endDocument(); 605 } 606 607 private static void persistRow(@NonNull final TypedXmlSerializer xml, 608 @NonNull final SettingsItem item) throws IOException { 609 xml.startTag(null, TAG_ITEM); 610 XmlUtils.writeStringAttribute(xml, ATTR_PACKAGE_NAME, item.mOverlay.getPackageName()); 611 XmlUtils.writeStringAttribute(xml, ATTR_OVERLAY_NAME, item.mOverlay.getOverlayName()); 612 xml.attributeInt(null, ATTR_USER_ID, item.mUserId); 613 XmlUtils.writeStringAttribute(xml, ATTR_TARGET_PACKAGE_NAME, item.mTargetPackageName); 614 XmlUtils.writeStringAttribute(xml, ATTR_TARGET_OVERLAYABLE_NAME, 615 item.mTargetOverlayableName); 616 XmlUtils.writeStringAttribute(xml, ATTR_BASE_CODE_PATH, item.mBaseCodePath); 617 xml.attributeInt(null, ATTR_STATE, item.mState); 618 XmlUtils.writeBooleanAttribute(xml, ATTR_IS_ENABLED, item.mIsEnabled); 619 XmlUtils.writeBooleanAttribute(xml, ATTR_IS_STATIC, !item.mIsMutable); 620 xml.attributeInt(null, ATTR_PRIORITY, item.mPriority); 621 XmlUtils.writeStringAttribute(xml, ATTR_CATEGORY, item.mCategory); 622 XmlUtils.writeBooleanAttribute(xml, ATTR_IS_FABRICATED, item.mIsFabricated); 623 624 for (OverlayConstraint constraint : item.mConstraints) { 625 xml.startTag(null, TAG_CONSTRAINT); 626 xml.attributeInt(null, ATTR_CONSTRAINT_TYPE, constraint.getType()); 627 xml.attributeInt(null, ATTR_CONSTRAINT_VALUE, constraint.getValue()); 628 xml.endTag(null, TAG_CONSTRAINT); 629 } 630 631 xml.endTag(null, TAG_ITEM); 632 } 633 } 634 635 private static final class SettingsItem { 636 private final int mUserId; 637 private final OverlayIdentifier mOverlay; 638 private final String mTargetPackageName; 639 private final String mTargetOverlayableName; 640 private String mBaseCodePath; 641 private @OverlayInfo.State int mState; 642 private boolean mIsEnabled; 643 private OverlayInfo mCache; 644 private final boolean mIsMutable; 645 private int mPriority; 646 private String mCategory; 647 private final boolean mIsFabricated; 648 @NonNull 649 private List<OverlayConstraint> mConstraints; 650 651 SettingsItem(@NonNull final OverlayIdentifier overlay, final int userId, 652 @NonNull final String targetPackageName, 653 @Nullable final String targetOverlayableName, @NonNull final String baseCodePath, 654 final @OverlayInfo.State int state, final boolean isEnabled, 655 final boolean isMutable, final int priority, @Nullable String category, 656 final boolean isFabricated, @NonNull final List<OverlayConstraint> constraints) { 657 mOverlay = overlay; 658 mUserId = userId; 659 mTargetPackageName = targetPackageName; 660 mTargetOverlayableName = targetOverlayableName; 661 mBaseCodePath = baseCodePath; 662 mState = state; 663 mIsEnabled = isEnabled; 664 mCategory = category; 665 mCache = null; 666 mIsMutable = isMutable; 667 mPriority = priority; 668 mIsFabricated = isFabricated; 669 Objects.requireNonNull(constraints); 670 mConstraints = constraints; 671 } 672 673 private String getTargetPackageName() { 674 return mTargetPackageName; 675 } 676 677 private String getTargetOverlayableName() { 678 return mTargetOverlayableName; 679 } 680 681 private int getUserId() { 682 return mUserId; 683 } 684 685 private String getBaseCodePath() { 686 return mBaseCodePath; 687 } 688 689 private boolean setBaseCodePath(@NonNull final String path) { 690 if (!mBaseCodePath.equals(path)) { 691 mBaseCodePath = path; 692 invalidateCache(); 693 return true; 694 } 695 return false; 696 } 697 698 private @OverlayInfo.State int getState() { 699 return mState; 700 } 701 702 private boolean setState(final @OverlayInfo.State int state) { 703 if (mState != state) { 704 mState = state; 705 invalidateCache(); 706 return true; 707 } 708 return false; 709 } 710 711 private boolean isEnabled() { 712 return mIsEnabled; 713 } 714 715 private boolean setEnabled(boolean enable) { 716 if (!mIsMutable) { 717 return false; 718 } 719 720 if (mIsEnabled != enable) { 721 mIsEnabled = enable; 722 invalidateCache(); 723 return true; 724 } 725 return false; 726 } 727 728 private boolean setCategory(String category) { 729 if (!Objects.equals(mCategory, category)) { 730 mCategory = (category == null) ? null : category.intern(); 731 invalidateCache(); 732 return true; 733 } 734 return false; 735 } 736 737 private boolean setConstraints(@NonNull List<OverlayConstraint> constraints) { 738 Objects.requireNonNull(constraints); 739 740 if (!mIsMutable) { 741 return false; 742 } 743 744 if (!Objects.equals(mConstraints, constraints)) { 745 mConstraints = constraints; 746 invalidateCache(); 747 return true; 748 } 749 return false; 750 } 751 752 private OverlayInfo getOverlayInfo() { 753 if (mCache == null) { 754 mCache = new OverlayInfo(mOverlay.getPackageName(), mOverlay.getOverlayName(), 755 mTargetPackageName, mTargetOverlayableName, mCategory, mBaseCodePath, 756 mState, mUserId, mPriority, mIsMutable, mIsFabricated, mConstraints); 757 } 758 return mCache; 759 } 760 761 private void setPriority(int priority) { 762 mPriority = priority; 763 invalidateCache(); 764 } 765 766 private void invalidateCache() { 767 mCache = null; 768 } 769 770 private boolean isMutable() { 771 return mIsMutable; 772 } 773 774 private int getPriority() { 775 return mPriority; 776 } 777 } 778 779 private int select(@NonNull final OverlayIdentifier overlay, final int userId) { 780 final int n = mItems.size(); 781 for (int i = 0; i < n; i++) { 782 final SettingsItem item = mItems.get(i); 783 if (item.mUserId == userId && item.mOverlay.equals(overlay)) { 784 return i; 785 } 786 } 787 return -1; 788 } 789 790 private List<SettingsItem> selectWhereUser(final int userId) { 791 final List<SettingsItem> selectedItems = new ArrayList<>(); 792 CollectionUtils.addIf(mItems, selectedItems, i -> i.mUserId == userId); 793 return selectedItems; 794 } 795 796 private List<SettingsItem> selectWhereOverlay(@NonNull final String packageName, 797 final int userId) { 798 final List<SettingsItem> items = selectWhereUser(userId); 799 items.removeIf(i -> !i.mOverlay.getPackageName().equals(packageName)); 800 return items; 801 } 802 803 private List<SettingsItem> selectWhereTarget(@NonNull final String targetPackageName, 804 final int userId) { 805 final List<SettingsItem> items = selectWhereUser(userId); 806 items.removeIf(i -> !i.getTargetPackageName().equals(targetPackageName)); 807 return items; 808 } 809 810 static final class BadKeyException extends Exception { 811 BadKeyException(@NonNull final OverlayIdentifier overlay, final int userId) { 812 super("Bad key '" + overlay + "' for user " + userId ); 813 } 814 } 815 } 816