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 17package com.android.server.om; 18 19import static com.android.server.om.OverlayManagerService.DEBUG; 20import static com.android.server.om.OverlayManagerService.TAG; 21 22import android.annotation.NonNull; 23import android.annotation.Nullable; 24import android.content.om.OverlayInfo; 25import android.os.UserHandle; 26import android.util.AndroidRuntimeException; 27import android.util.ArrayMap; 28import android.util.Slog; 29import android.util.Xml; 30 31import com.android.internal.util.FastXmlSerializer; 32import com.android.internal.util.IndentingPrintWriter; 33import com.android.internal.util.XmlUtils; 34 35import org.xmlpull.v1.XmlPullParser; 36import org.xmlpull.v1.XmlPullParserException; 37 38import java.io.IOException; 39import java.io.InputStream; 40import java.io.InputStreamReader; 41import java.io.OutputStream; 42import java.io.PrintWriter; 43import java.util.ArrayList; 44import java.util.List; 45import java.util.stream.Collectors; 46import 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 */ 56final 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