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