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