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 init(@onNull final String packageName, final int userId, @NonNull final String targetPackageName, @NonNull final String baseCodePath, boolean isStatic, int priority, String overlayCategory)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 */ remove(@onNull final String packageName, final int userId)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 getOverlayInfo(@onNull final String packageName, final int userId)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 */ setBaseCodePath(@onNull final String packageName, final int userId, @NonNull final String path)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 setCategory(@onNull final String packageName, final int userId, @Nullable String category)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 getEnabled(@onNull final String packageName, final int userId)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 */ setEnabled(@onNull final String packageName, final int userId, final boolean enable)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 getState(@onNull final String packageName, final int userId)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 */ setState(@onNull final String packageName, final int userId, final @OverlayInfo.State int state)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 getOverlaysForTarget(@onNull final String targetPackageName, final int userId)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 getOverlaysForUser(final int userId)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 getUsers()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 */ removeUser(final int userId)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 */ setPriority(@onNull final String packageName, @NonNull final String newParentPackageName, final int userId)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 */ setLowestPriority(@onNull final String packageName, final int userId)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 */ setHighestPriority(@onNull final String packageName, final int userId)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 dump(@onNull final PrintWriter p)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 restore(@onNull final InputStream is)320 void restore(@NonNull final InputStream is) throws IOException, XmlPullParserException { 321 Serializer.restore(mItems, is); 322 } 323 persist(@onNull final OutputStream os)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 restore(@onNull final ArrayList<SettingsItem> table, @NonNull final InputStream is)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 upgrade(int oldVersion)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 restoreRow(@onNull final XmlPullParser parser, final int depth)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 persist(@onNull final ArrayList<SettingsItem> table, @NonNull final OutputStream os)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 persistRow(@onNull final FastXmlSerializer xml, @NonNull final SettingsItem item)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 SettingsItem(@onNull final String packageName, final int userId, @NonNull final String targetPackageName, @NonNull final String baseCodePath, final @OverlayInfo.State int state, final boolean isEnabled, final boolean isStatic, final int priority, String category)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 SettingsItem(@onNull final String packageName, final int userId, @NonNull final String targetPackageName, @NonNull final String baseCodePath, final boolean isStatic, final int priority, String category)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 getTargetPackageName()469 private String getTargetPackageName() { 470 return mTargetPackageName; 471 } 472 getUserId()473 private int getUserId() { 474 return mUserId; 475 } 476 getBaseCodePath()477 private String getBaseCodePath() { 478 return mBaseCodePath; 479 } 480 setBaseCodePath(@onNull final String path)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 getState()490 private @OverlayInfo.State int getState() { 491 return mState; 492 } 493 setState(final @OverlayInfo.State int state)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 isEnabled()503 private boolean isEnabled() { 504 return mIsEnabled; 505 } 506 setEnabled(boolean enable)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 setCategory(String category)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 getOverlayInfo()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 invalidateCache()537 private void invalidateCache() { 538 mCache = null; 539 } 540 isStatic()541 private boolean isStatic() { 542 return mIsStatic; 543 } 544 getPriority()545 private int getPriority() { 546 return mPriority; 547 } 548 } 549 select(@onNull final String packageName, final int userId)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 selectWhereUser(final int userId)561 private Stream<SettingsItem> selectWhereUser(final int userId) { 562 return mItems.stream().filter(item -> item.mUserId == userId); 563 } 564 selectWhereTarget(@onNull final String targetPackageName, final int userId)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 { BadKeyException(@onNull final String packageName, final int userId)572 BadKeyException(@NonNull final String packageName, final int userId) { 573 super("Bad key mPackageName=" + packageName + " mUserId=" + userId); 574 } 575 } 576 } 577