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.OverlayInfo; 25 import android.os.UserHandle; 26 import android.util.ArrayMap; 27 import android.util.Slog; 28 import android.util.Xml; 29 30 import com.android.internal.annotations.VisibleForTesting; 31 import com.android.internal.util.FastXmlSerializer; 32 import com.android.internal.util.IndentingPrintWriter; 33 import com.android.internal.util.XmlUtils; 34 35 import org.xmlpull.v1.XmlPullParser; 36 import org.xmlpull.v1.XmlPullParserException; 37 38 import java.io.IOException; 39 import java.io.InputStream; 40 import java.io.InputStreamReader; 41 import java.io.OutputStream; 42 import java.io.PrintWriter; 43 import java.util.ArrayList; 44 import java.util.List; 45 import java.util.Objects; 46 import java.util.stream.Collectors; 47 import java.util.stream.Stream; 48 49 /** 50 * Data structure representing the current state of all overlay packages in the 51 * system. 52 * 53 * Modifications to the data are signaled by returning true from any state mutating method. 54 * 55 * @see OverlayManagerService 56 */ 57 final class OverlayManagerSettings { 58 /** 59 * All overlay data for all users and target packages is stored in this list. 60 * This keeps memory down, while increasing the cost of running queries or mutating the 61 * data. This is ok, since changing of overlays is very rare and has larger costs associated 62 * with it. 63 * 64 * The order of the items in the list is important, those with a lower index having a lower 65 * priority. 66 */ 67 private final ArrayList<SettingsItem> mItems = new ArrayList<>(); 68 init(@onNull final String packageName, final int userId, @NonNull final String targetPackageName, @Nullable final String targetOverlayableName, @NonNull final String baseCodePath, boolean isStatic, int priority, @Nullable String overlayCategory)69 void init(@NonNull final String packageName, final int userId, 70 @NonNull final String targetPackageName, @Nullable final String targetOverlayableName, 71 @NonNull final String baseCodePath, boolean isStatic, int priority, 72 @Nullable String overlayCategory) { 73 remove(packageName, userId); 74 final SettingsItem item = 75 new SettingsItem(packageName, userId, targetPackageName, targetOverlayableName, 76 baseCodePath, isStatic, priority, overlayCategory); 77 if (isStatic) { 78 // All static overlays are always enabled. 79 item.setEnabled(true); 80 81 int i; 82 for (i = mItems.size() - 1; i >= 0; i--) { 83 SettingsItem parentItem = mItems.get(i); 84 if (parentItem.mIsStatic && parentItem.mPriority <= priority) { 85 break; 86 } 87 } 88 int pos = i + 1; 89 if (pos == mItems.size()) { 90 mItems.add(item); 91 } else { 92 mItems.add(pos, item); 93 } 94 } else { 95 mItems.add(item); 96 } 97 } 98 99 /** 100 * Returns true if the settings were modified, false if they remain the same. 101 */ remove(@onNull final String packageName, final int userId)102 boolean remove(@NonNull final String packageName, final int userId) { 103 final int idx = select(packageName, userId); 104 if (idx < 0) { 105 return false; 106 } 107 108 mItems.remove(idx); 109 return true; 110 } 111 getOverlayInfo(@onNull final String packageName, final int userId)112 @NonNull OverlayInfo getOverlayInfo(@NonNull final String packageName, final int userId) 113 throws BadKeyException { 114 final int idx = select(packageName, userId); 115 if (idx < 0) { 116 throw new BadKeyException(packageName, userId); 117 } 118 return mItems.get(idx).getOverlayInfo(); 119 } 120 121 /** 122 * Returns true if the settings were modified, false if they remain the same. 123 */ setBaseCodePath(@onNull final String packageName, final int userId, @NonNull final String path)124 boolean setBaseCodePath(@NonNull final String packageName, final int userId, 125 @NonNull final String path) throws BadKeyException { 126 final int idx = select(packageName, userId); 127 if (idx < 0) { 128 throw new BadKeyException(packageName, userId); 129 } 130 return mItems.get(idx).setBaseCodePath(path); 131 } 132 setCategory(@onNull final String packageName, final int userId, @Nullable String category)133 boolean setCategory(@NonNull final String packageName, final int userId, 134 @Nullable String category) throws BadKeyException { 135 final int idx = select(packageName, userId); 136 if (idx < 0) { 137 throw new BadKeyException(packageName, userId); 138 } 139 return mItems.get(idx).setCategory(category); 140 } 141 getEnabled(@onNull final String packageName, final int userId)142 boolean getEnabled(@NonNull final String packageName, final int userId) throws BadKeyException { 143 final int idx = select(packageName, userId); 144 if (idx < 0) { 145 throw new BadKeyException(packageName, userId); 146 } 147 return mItems.get(idx).isEnabled(); 148 } 149 150 /** 151 * Returns true if the settings were modified, false if they remain the same. 152 */ setEnabled(@onNull final String packageName, final int userId, final boolean enable)153 boolean setEnabled(@NonNull final String packageName, final int userId, final boolean enable) 154 throws BadKeyException { 155 final int idx = select(packageName, userId); 156 if (idx < 0) { 157 throw new BadKeyException(packageName, userId); 158 } 159 return mItems.get(idx).setEnabled(enable); 160 } 161 getState(@onNull final String packageName, final int userId)162 @OverlayInfo.State int getState(@NonNull final String packageName, final int userId) 163 throws BadKeyException { 164 final int idx = select(packageName, userId); 165 if (idx < 0) { 166 throw new BadKeyException(packageName, userId); 167 } 168 return mItems.get(idx).getState(); 169 } 170 171 /** 172 * Returns true if the settings were modified, false if they remain the same. 173 */ setState(@onNull final String packageName, final int userId, final @OverlayInfo.State int state)174 boolean setState(@NonNull final String packageName, final int userId, 175 final @OverlayInfo.State int state) throws BadKeyException { 176 final int idx = select(packageName, userId); 177 if (idx < 0) { 178 throw new BadKeyException(packageName, userId); 179 } 180 return mItems.get(idx).setState(state); 181 } 182 getOverlaysForTarget(@onNull final String targetPackageName, final int userId)183 List<OverlayInfo> getOverlaysForTarget(@NonNull final String targetPackageName, 184 final int userId) { 185 // Static RROs targeting "android" are loaded from AssetManager, and so they should be 186 // ignored in OverlayManagerService. 187 return selectWhereTarget(targetPackageName, userId) 188 .filter((i) -> !(i.isStatic() && "android".equals(i.getTargetPackageName()))) 189 .map(SettingsItem::getOverlayInfo) 190 .collect(Collectors.toList()); 191 } 192 getOverlaysForUser(final int userId)193 ArrayMap<String, List<OverlayInfo>> getOverlaysForUser(final int userId) { 194 // Static RROs targeting "android" are loaded from AssetManager, and so they should be 195 // ignored in OverlayManagerService. 196 return selectWhereUser(userId) 197 .filter((i) -> !(i.isStatic() && "android".equals(i.getTargetPackageName()))) 198 .map(SettingsItem::getOverlayInfo) 199 .collect(Collectors.groupingBy(info -> info.targetPackageName, ArrayMap::new, 200 Collectors.toList())); 201 } 202 getUsers()203 int[] getUsers() { 204 return mItems.stream().mapToInt(SettingsItem::getUserId).distinct().toArray(); 205 } 206 207 /** 208 * Returns true if the settings were modified, false if they remain the same. 209 */ removeUser(final int userId)210 boolean removeUser(final int userId) { 211 boolean removed = false; 212 for (int i = 0; i < mItems.size(); i++) { 213 final SettingsItem item = mItems.get(i); 214 if (item.getUserId() == userId) { 215 if (DEBUG) { 216 Slog.d(TAG, "Removing overlay " + item.mPackageName + " for user " + userId 217 + " from settings because user was removed"); 218 } 219 mItems.remove(i); 220 removed = true; 221 i--; 222 } 223 } 224 return removed; 225 } 226 227 /** 228 * Returns true if the settings were modified, false if they remain the same. 229 */ setPriority(@onNull final String packageName, @NonNull final String newParentPackageName, final int userId)230 boolean setPriority(@NonNull final String packageName, 231 @NonNull final String newParentPackageName, final int userId) { 232 if (packageName.equals(newParentPackageName)) { 233 return false; 234 } 235 final int moveIdx = select(packageName, userId); 236 if (moveIdx < 0) { 237 return false; 238 } 239 240 final int parentIdx = select(newParentPackageName, userId); 241 if (parentIdx < 0) { 242 return false; 243 } 244 245 final SettingsItem itemToMove = mItems.get(moveIdx); 246 247 // Make sure both packages are targeting the same package. 248 if (!itemToMove.getTargetPackageName().equals( 249 mItems.get(parentIdx).getTargetPackageName())) { 250 return false; 251 } 252 253 mItems.remove(moveIdx); 254 final int newParentIdx = select(newParentPackageName, userId) + 1; 255 mItems.add(newParentIdx, itemToMove); 256 return moveIdx != newParentIdx; 257 } 258 259 /** 260 * Returns true if the settings were modified, false if they remain the same. 261 */ setLowestPriority(@onNull final String packageName, final int userId)262 boolean setLowestPriority(@NonNull final String packageName, final int userId) { 263 final int idx = select(packageName, userId); 264 if (idx <= 0) { 265 // If the item doesn't exist or is already the lowest, don't change anything. 266 return false; 267 } 268 269 final SettingsItem item = mItems.get(idx); 270 mItems.remove(item); 271 mItems.add(0, item); 272 return true; 273 } 274 275 /** 276 * Returns true if the settings were modified, false if they remain the same. 277 */ setHighestPriority(@onNull final String packageName, final int userId)278 boolean setHighestPriority(@NonNull final String packageName, final int userId) { 279 final int idx = select(packageName, userId); 280 281 // If the item doesn't exist or is already the highest, don't change anything. 282 if (idx < 0 || idx == mItems.size() - 1) { 283 return false; 284 } 285 286 final SettingsItem item = mItems.get(idx); 287 mItems.remove(idx); 288 mItems.add(item); 289 return true; 290 } 291 dump(@onNull final PrintWriter p, @NonNull DumpState dumpState)292 void dump(@NonNull final PrintWriter p, @NonNull DumpState dumpState) { 293 // select items to display 294 Stream<SettingsItem> items = mItems.stream(); 295 if (dumpState.getUserId() != UserHandle.USER_ALL) { 296 items = items.filter(item -> item.mUserId == dumpState.getUserId()); 297 } 298 if (dumpState.getPackageName() != null) { 299 items = items.filter(item -> item.mPackageName.equals(dumpState.getPackageName())); 300 } 301 302 // display items 303 final IndentingPrintWriter pw = new IndentingPrintWriter(p, " "); 304 if (dumpState.getField() != null) { 305 items.forEach(item -> dumpSettingsItemField(pw, item, dumpState.getField())); 306 } else { 307 items.forEach(item -> dumpSettingsItem(pw, item)); 308 } 309 } 310 dumpSettingsItem(@onNull final IndentingPrintWriter pw, @NonNull final SettingsItem item)311 private void dumpSettingsItem(@NonNull final IndentingPrintWriter pw, 312 @NonNull final SettingsItem item) { 313 pw.println(item.mPackageName + ":" + item.getUserId() + " {"); 314 pw.increaseIndent(); 315 316 pw.println("mPackageName...........: " + item.mPackageName); 317 pw.println("mUserId................: " + item.getUserId()); 318 pw.println("mTargetPackageName.....: " + item.getTargetPackageName()); 319 pw.println("mTargetOverlayableName.: " + item.getTargetOverlayableName()); 320 pw.println("mBaseCodePath..........: " + item.getBaseCodePath()); 321 pw.println("mState.................: " + OverlayInfo.stateToString(item.getState())); 322 pw.println("mIsEnabled.............: " + item.isEnabled()); 323 pw.println("mIsStatic..............: " + item.isStatic()); 324 pw.println("mPriority..............: " + item.mPriority); 325 pw.println("mCategory..............: " + item.mCategory); 326 327 pw.decreaseIndent(); 328 pw.println("}"); 329 } 330 dumpSettingsItemField(@onNull final IndentingPrintWriter pw, @NonNull final SettingsItem item, @NonNull final String field)331 private void dumpSettingsItemField(@NonNull final IndentingPrintWriter pw, 332 @NonNull final SettingsItem item, @NonNull final String field) { 333 switch (field) { 334 case "packagename": 335 pw.println(item.mPackageName); 336 break; 337 case "userid": 338 pw.println(item.mUserId); 339 break; 340 case "targetpackagename": 341 pw.println(item.mTargetPackageName); 342 break; 343 case "targetoverlayablename": 344 pw.println(item.mTargetOverlayableName); 345 break; 346 case "basecodepath": 347 pw.println(item.mBaseCodePath); 348 break; 349 case "state": 350 pw.println(OverlayInfo.stateToString(item.mState)); 351 break; 352 case "isenabled": 353 pw.println(item.mIsEnabled); 354 break; 355 case "isstatic": 356 pw.println(item.mIsStatic); 357 break; 358 case "priority": 359 pw.println(item.mPriority); 360 break; 361 case "category": 362 pw.println(item.mCategory); 363 break; 364 } 365 } 366 restore(@onNull final InputStream is)367 void restore(@NonNull final InputStream is) throws IOException, XmlPullParserException { 368 Serializer.restore(mItems, is); 369 } 370 persist(@onNull final OutputStream os)371 void persist(@NonNull final OutputStream os) throws IOException, XmlPullParserException { 372 Serializer.persist(mItems, os); 373 } 374 375 @VisibleForTesting 376 static final class Serializer { 377 private static final String TAG_OVERLAYS = "overlays"; 378 private static final String TAG_ITEM = "item"; 379 380 private static final String ATTR_BASE_CODE_PATH = "baseCodePath"; 381 private static final String ATTR_IS_ENABLED = "isEnabled"; 382 private static final String ATTR_PACKAGE_NAME = "packageName"; 383 private static final String ATTR_STATE = "state"; 384 private static final String ATTR_TARGET_PACKAGE_NAME = "targetPackageName"; 385 private static final String ATTR_TARGET_OVERLAYABLE_NAME = "targetOverlayableName"; 386 private static final String ATTR_IS_STATIC = "isStatic"; 387 private static final String ATTR_PRIORITY = "priority"; 388 private static final String ATTR_CATEGORY = "category"; 389 private static final String ATTR_USER_ID = "userId"; 390 private static final String ATTR_VERSION = "version"; 391 392 @VisibleForTesting 393 static final int CURRENT_VERSION = 3; 394 restore(@onNull final ArrayList<SettingsItem> table, @NonNull final InputStream is)395 public static void restore(@NonNull final ArrayList<SettingsItem> table, 396 @NonNull final InputStream is) throws IOException, XmlPullParserException { 397 398 try (InputStreamReader reader = new InputStreamReader(is)) { 399 table.clear(); 400 final XmlPullParser parser = Xml.newPullParser(); 401 parser.setInput(reader); 402 XmlUtils.beginDocument(parser, TAG_OVERLAYS); 403 int version = XmlUtils.readIntAttribute(parser, ATTR_VERSION); 404 if (version != CURRENT_VERSION) { 405 upgrade(version); 406 } 407 int depth = parser.getDepth(); 408 409 while (XmlUtils.nextElementWithin(parser, depth)) { 410 switch (parser.getName()) { 411 case TAG_ITEM: 412 final SettingsItem item = restoreRow(parser, depth + 1); 413 table.add(item); 414 break; 415 } 416 } 417 } 418 } 419 upgrade(int oldVersion)420 private static void upgrade(int oldVersion) throws XmlPullParserException { 421 switch (oldVersion) { 422 case 0: 423 case 1: 424 case 2: 425 // Throw an exception which will cause the overlay file to be ignored 426 // and overwritten. 427 throw new XmlPullParserException("old version " + oldVersion + "; ignoring"); 428 default: 429 throw new XmlPullParserException("unrecognized version " + oldVersion); 430 } 431 } 432 restoreRow(@onNull final XmlPullParser parser, final int depth)433 private static SettingsItem restoreRow(@NonNull final XmlPullParser parser, final int depth) 434 throws IOException { 435 final String packageName = XmlUtils.readStringAttribute(parser, ATTR_PACKAGE_NAME); 436 final int userId = XmlUtils.readIntAttribute(parser, ATTR_USER_ID); 437 final String targetPackageName = XmlUtils.readStringAttribute(parser, 438 ATTR_TARGET_PACKAGE_NAME); 439 final String targetOverlayableName = XmlUtils.readStringAttribute(parser, 440 ATTR_TARGET_OVERLAYABLE_NAME); 441 final String baseCodePath = XmlUtils.readStringAttribute(parser, ATTR_BASE_CODE_PATH); 442 final int state = XmlUtils.readIntAttribute(parser, ATTR_STATE); 443 final boolean isEnabled = XmlUtils.readBooleanAttribute(parser, ATTR_IS_ENABLED); 444 final boolean isStatic = XmlUtils.readBooleanAttribute(parser, ATTR_IS_STATIC); 445 final int priority = XmlUtils.readIntAttribute(parser, ATTR_PRIORITY); 446 final String category = XmlUtils.readStringAttribute(parser, ATTR_CATEGORY); 447 448 return new SettingsItem(packageName, userId, targetPackageName, targetOverlayableName, 449 baseCodePath, state, isEnabled, isStatic, priority, category); 450 } 451 persist(@onNull final ArrayList<SettingsItem> table, @NonNull final OutputStream os)452 public static void persist(@NonNull final ArrayList<SettingsItem> table, 453 @NonNull final OutputStream os) throws IOException, XmlPullParserException { 454 final FastXmlSerializer xml = new FastXmlSerializer(); 455 xml.setOutput(os, "utf-8"); 456 xml.startDocument(null, true); 457 xml.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); 458 xml.startTag(null, TAG_OVERLAYS); 459 XmlUtils.writeIntAttribute(xml, ATTR_VERSION, CURRENT_VERSION); 460 461 final int n = table.size(); 462 for (int i = 0; i < n; i++) { 463 final SettingsItem item = table.get(i); 464 persistRow(xml, item); 465 } 466 xml.endTag(null, TAG_OVERLAYS); 467 xml.endDocument(); 468 } 469 persistRow(@onNull final FastXmlSerializer xml, @NonNull final SettingsItem item)470 private static void persistRow(@NonNull final FastXmlSerializer xml, 471 @NonNull final SettingsItem item) throws IOException { 472 xml.startTag(null, TAG_ITEM); 473 XmlUtils.writeStringAttribute(xml, ATTR_PACKAGE_NAME, item.mPackageName); 474 XmlUtils.writeIntAttribute(xml, ATTR_USER_ID, item.mUserId); 475 XmlUtils.writeStringAttribute(xml, ATTR_TARGET_PACKAGE_NAME, item.mTargetPackageName); 476 XmlUtils.writeStringAttribute(xml, ATTR_TARGET_OVERLAYABLE_NAME, 477 item.mTargetOverlayableName); 478 XmlUtils.writeStringAttribute(xml, ATTR_BASE_CODE_PATH, item.mBaseCodePath); 479 XmlUtils.writeIntAttribute(xml, ATTR_STATE, item.mState); 480 XmlUtils.writeBooleanAttribute(xml, ATTR_IS_ENABLED, item.mIsEnabled); 481 XmlUtils.writeBooleanAttribute(xml, ATTR_IS_STATIC, item.mIsStatic); 482 XmlUtils.writeIntAttribute(xml, ATTR_PRIORITY, item.mPriority); 483 XmlUtils.writeStringAttribute(xml, ATTR_CATEGORY, item.mCategory); 484 xml.endTag(null, TAG_ITEM); 485 } 486 } 487 488 private static final class SettingsItem { 489 private final int mUserId; 490 private final String mPackageName; 491 private final String mTargetPackageName; 492 private final String mTargetOverlayableName; 493 private String mBaseCodePath; 494 private @OverlayInfo.State int mState; 495 private boolean mIsEnabled; 496 private OverlayInfo mCache; 497 private boolean mIsStatic; 498 private int mPriority; 499 private String mCategory; 500 SettingsItem(@onNull final String packageName, final int userId, @NonNull final String targetPackageName, @Nullable final String targetOverlayableName, @NonNull final String baseCodePath, final @OverlayInfo.State int state, final boolean isEnabled, final boolean isStatic, final int priority, @Nullable String category)501 SettingsItem(@NonNull final String packageName, final int userId, 502 @NonNull final String targetPackageName, 503 @Nullable final String targetOverlayableName, @NonNull final String baseCodePath, 504 final @OverlayInfo.State int state, final boolean isEnabled, final boolean isStatic, 505 final int priority, @Nullable String category) { 506 mPackageName = packageName; 507 mUserId = userId; 508 mTargetPackageName = targetPackageName; 509 mTargetOverlayableName = targetOverlayableName; 510 mBaseCodePath = baseCodePath; 511 mState = state; 512 mIsEnabled = isEnabled || isStatic; 513 mCategory = category; 514 mCache = null; 515 mIsStatic = isStatic; 516 mPriority = priority; 517 } 518 SettingsItem(@onNull final String packageName, final int userId, @NonNull final String targetPackageName, @Nullable final String targetOverlayableName, @NonNull final String baseCodePath, final boolean isStatic, final int priority, @Nullable String category)519 SettingsItem(@NonNull final String packageName, final int userId, 520 @NonNull final String targetPackageName, 521 @Nullable final String targetOverlayableName, @NonNull final String baseCodePath, 522 final boolean isStatic, final int priority, @Nullable String category) { 523 this(packageName, userId, targetPackageName, targetOverlayableName, baseCodePath, 524 OverlayInfo.STATE_UNKNOWN, false, isStatic, priority, category); 525 } 526 getTargetPackageName()527 private String getTargetPackageName() { 528 return mTargetPackageName; 529 } 530 getTargetOverlayableName()531 private String getTargetOverlayableName() { 532 return mTargetOverlayableName; 533 } 534 getUserId()535 private int getUserId() { 536 return mUserId; 537 } 538 getBaseCodePath()539 private String getBaseCodePath() { 540 return mBaseCodePath; 541 } 542 setBaseCodePath(@onNull final String path)543 private boolean setBaseCodePath(@NonNull final String path) { 544 if (!mBaseCodePath.equals(path)) { 545 mBaseCodePath = path; 546 invalidateCache(); 547 return true; 548 } 549 return false; 550 } 551 getState()552 private @OverlayInfo.State int getState() { 553 return mState; 554 } 555 setState(final @OverlayInfo.State int state)556 private boolean setState(final @OverlayInfo.State int state) { 557 if (mState != state) { 558 mState = state; 559 invalidateCache(); 560 return true; 561 } 562 return false; 563 } 564 isEnabled()565 private boolean isEnabled() { 566 return mIsEnabled; 567 } 568 setEnabled(boolean enable)569 private boolean setEnabled(boolean enable) { 570 if (mIsStatic) { 571 return false; 572 } 573 574 if (mIsEnabled != enable) { 575 mIsEnabled = enable; 576 invalidateCache(); 577 return true; 578 } 579 return false; 580 } 581 setCategory(String category)582 private boolean setCategory(String category) { 583 if (!Objects.equals(mCategory, category)) { 584 mCategory = (category == null) ? null : category.intern(); 585 invalidateCache(); 586 return true; 587 } 588 return false; 589 } 590 getOverlayInfo()591 private OverlayInfo getOverlayInfo() { 592 if (mCache == null) { 593 mCache = new OverlayInfo(mPackageName, mTargetPackageName, mTargetOverlayableName, 594 mCategory, mBaseCodePath, mState, mUserId, mPriority, mIsStatic); 595 } 596 return mCache; 597 } 598 invalidateCache()599 private void invalidateCache() { 600 mCache = null; 601 } 602 isStatic()603 private boolean isStatic() { 604 return mIsStatic; 605 } 606 getPriority()607 private int getPriority() { 608 return mPriority; 609 } 610 } 611 select(@onNull final String packageName, final int userId)612 private int select(@NonNull final String packageName, final int userId) { 613 final int n = mItems.size(); 614 for (int i = 0; i < n; i++) { 615 final SettingsItem item = mItems.get(i); 616 if (item.mUserId == userId && item.mPackageName.equals(packageName)) { 617 return i; 618 } 619 } 620 return -1; 621 } 622 selectWhereUser(final int userId)623 private Stream<SettingsItem> selectWhereUser(final int userId) { 624 return mItems.stream().filter(item -> item.mUserId == userId); 625 } 626 selectWhereTarget(@onNull final String targetPackageName, final int userId)627 private Stream<SettingsItem> selectWhereTarget(@NonNull final String targetPackageName, 628 final int userId) { 629 return selectWhereUser(userId) 630 .filter(item -> item.getTargetPackageName().equals(targetPackageName)); 631 } 632 633 static final class BadKeyException extends RuntimeException { BadKeyException(@onNull final String packageName, final int userId)634 BadKeyException(@NonNull final String packageName, final int userId) { 635 super("Bad key mPackageName=" + packageName + " mUserId=" + userId); 636 } 637 } 638 } 639