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