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