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