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