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.internal.content.om.OverlayConfig.PARTITION_ORDER_FILE_PATH; 20 21 import android.annotation.NonNull; 22 import android.annotation.Nullable; 23 import android.content.Context; 24 import android.content.om.FabricatedOverlay; 25 import android.content.om.IOverlayManager; 26 import android.content.om.OverlayIdentifier; 27 import android.content.om.OverlayInfo; 28 import android.content.om.OverlayManagerTransaction; 29 import android.content.pm.PackageManager; 30 import android.content.res.AssetManager; 31 import android.content.res.Resources; 32 import android.content.res.TypedArray; 33 import android.os.Binder; 34 import android.os.ParcelFileDescriptor; 35 import android.os.Process; 36 import android.os.RemoteException; 37 import android.os.ShellCommand; 38 import android.os.UserHandle; 39 import android.text.TextUtils; 40 import android.util.TypedValue; 41 import android.util.Xml; 42 43 import com.android.modules.utils.TypedXmlPullParser; 44 45 import org.xmlpull.v1.XmlPullParser; 46 import org.xmlpull.v1.XmlPullParserException; 47 48 import java.io.File; 49 import java.io.FileInputStream; 50 import java.io.IOException; 51 import java.io.PrintWriter; 52 import java.util.List; 53 import java.util.Locale; 54 import java.util.Map; 55 import java.util.regex.Matcher; 56 import java.util.regex.Pattern; 57 58 /** 59 * Implementation of 'cmd overlay' commands. 60 * 61 * This class provides an interface to the OverlayManagerService via adb. 62 * Intended only for manual debugging. Execute 'adb exec-out cmd overlay help' 63 * for a list of available commands. 64 */ 65 final class OverlayManagerShellCommand extends ShellCommand { 66 private final Context mContext; 67 private final IOverlayManager mInterface; 68 private static final Map<String, Integer> TYPE_MAP = Map.of( 69 "color", TypedValue.TYPE_FIRST_COLOR_INT, 70 "string", TypedValue.TYPE_STRING, 71 "drawable", -1); 72 OverlayManagerShellCommand(@onNull final Context ctx, @NonNull final IOverlayManager iom)73 OverlayManagerShellCommand(@NonNull final Context ctx, @NonNull final IOverlayManager iom) { 74 mContext = ctx; 75 mInterface = iom; 76 } 77 78 @Override onCommand(@ullable final String cmd)79 public int onCommand(@Nullable final String cmd) { 80 if (cmd == null) { 81 return handleDefaultCommands(cmd); 82 } 83 final PrintWriter err = getErrPrintWriter(); 84 try { 85 switch (cmd) { 86 case "list": 87 return runList(); 88 case "enable": 89 return runEnableDisable(true); 90 case "disable": 91 return runEnableDisable(false); 92 case "enable-exclusive": 93 return runEnableExclusive(); 94 case "set-priority": 95 return runSetPriority(); 96 case "lookup": 97 return runLookup(); 98 case "fabricate": 99 return runFabricate(); 100 case "partition-order": 101 return runPartitionOrder(); 102 default: 103 return handleDefaultCommands(cmd); 104 } 105 } catch (IllegalArgumentException e) { 106 err.println("Error: " + e.getMessage()); 107 } catch (RemoteException e) { 108 err.println("Remote exception: " + e); 109 } 110 return -1; 111 } 112 113 @Override onHelp()114 public void onHelp() { 115 final PrintWriter out = getOutPrintWriter(); 116 out.println("Overlay manager (overlay) commands:"); 117 out.println(" help"); 118 out.println(" Print this help text."); 119 out.println(" dump [--verbose] [--user USER_ID] [[FIELD] PACKAGE[:NAME]]"); 120 out.println(" Print debugging information about the overlay manager."); 121 out.println(" With optional parameters PACKAGE and NAME, limit output to the specified"); 122 out.println(" overlay or target. With optional parameter FIELD, limit output to"); 123 out.println(" the corresponding SettingsItem field. Field names are all lower case"); 124 out.println(" and omit the m prefix, i.e. 'userid' for SettingsItem.mUserId."); 125 out.println(" list [--user USER_ID] [PACKAGE[:NAME]]"); 126 out.println(" Print information about target and overlay packages."); 127 out.println(" Overlay packages are printed in priority order. With optional"); 128 out.println(" parameters PACKAGE and NAME, limit output to the specified overlay or"); 129 out.println(" target."); 130 out.println(" enable [--user USER_ID] PACKAGE[:NAME]"); 131 out.println(" Enable overlay within or owned by PACKAGE with optional unique NAME."); 132 out.println(" disable [--user USER_ID] PACKAGE[:NAME]"); 133 out.println(" Disable overlay within or owned by PACKAGE with optional unique NAME."); 134 out.println(" enable-exclusive [--user USER_ID] [--category] PACKAGE"); 135 out.println(" Enable overlay within or owned by PACKAGE and disable all other overlays"); 136 out.println(" for its target package. If the --category option is given, only disables"); 137 out.println(" other overlays in the same category."); 138 out.println(" set-priority [--user USER_ID] PACKAGE PARENT|lowest|highest"); 139 out.println(" Change the priority of the overlay to be just higher than"); 140 out.println(" the priority of PARENT If PARENT is the special keyword"); 141 out.println(" 'lowest', change priority of PACKAGE to the lowest priority."); 142 out.println(" If PARENT is the special keyword 'highest', change priority of"); 143 out.println(" PACKAGE to the highest priority."); 144 out.println(" lookup [--user USER_ID] [--verbose] PACKAGE-TO-LOAD PACKAGE:TYPE/NAME"); 145 out.println(" Load a package and print the value of a given resource"); 146 out.println(" applying the current configuration and enabled overlays."); 147 out.println(" For a more fine-grained alternative, use 'idmap2 lookup'."); 148 out.println(" fabricate [--user USER_ID] [--target-name OVERLAYABLE] --target PACKAGE"); 149 out.println(" --name NAME [--file FILE] "); 150 out.println(" PACKAGE:TYPE/NAME ENCODED-TYPE-ID|TYPE-NAME ENCODED-VALUE"); 151 out.println(" Create an overlay from a single resource. Caller must be root. Example:"); 152 out.println(" fabricate --target android --name LighterGray \\"); 153 out.println(" android:color/lighter_gray 0x1c 0xffeeeeee"); 154 out.println(" partition-order"); 155 out.println(" Print the partition order from overlay config and how this order"); 156 out.println(" got established, by default or by " + PARTITION_ORDER_FILE_PATH); 157 } 158 runList()159 private int runList() throws RemoteException { 160 final PrintWriter out = getOutPrintWriter(); 161 final PrintWriter err = getErrPrintWriter(); 162 163 int userId = UserHandle.USER_SYSTEM; 164 String opt; 165 while ((opt = getNextOption()) != null) { 166 switch (opt) { 167 case "--user": 168 userId = UserHandle.parseUserArg(getNextArgRequired()); 169 break; 170 default: 171 err.println("Error: Unknown option: " + opt); 172 return 1; 173 } 174 } 175 176 final String packageName = getNextArg(); 177 if (packageName != null) { 178 List<OverlayInfo> overlaysForTarget = mInterface.getOverlayInfosForTarget( 179 packageName, userId); 180 181 // If the package is not targeted by any overlays, check if the package is an overlay. 182 if (overlaysForTarget.isEmpty()) { 183 final OverlayInfo info = mInterface.getOverlayInfo(packageName, userId); 184 if (info != null) { 185 printListOverlay(out, info); 186 } 187 return 0; 188 } 189 190 out.println(packageName); 191 192 // Print the overlays for the target. 193 final int n = overlaysForTarget.size(); 194 for (int i = 0; i < n; i++) { 195 printListOverlay(out, overlaysForTarget.get(i)); 196 } 197 198 return 0; 199 } 200 201 // Print all overlays grouped by target package name. 202 final Map<String, List<OverlayInfo>> allOverlays = mInterface.getAllOverlays(userId); 203 for (final String targetPackageName : allOverlays.keySet()) { 204 out.println(targetPackageName); 205 206 List<OverlayInfo> overlaysForTarget = allOverlays.get(targetPackageName); 207 final int n = overlaysForTarget.size(); 208 for (int i = 0; i < n; i++) { 209 printListOverlay(out, overlaysForTarget.get(i)); 210 } 211 out.println(); 212 } 213 214 return 0; 215 } 216 printListOverlay(PrintWriter out, OverlayInfo oi)217 private void printListOverlay(PrintWriter out, OverlayInfo oi) { 218 String status; 219 switch (oi.state) { 220 case OverlayInfo.STATE_ENABLED_IMMUTABLE: 221 case OverlayInfo.STATE_ENABLED: 222 status = "[x]"; 223 break; 224 case OverlayInfo.STATE_DISABLED: 225 status = "[ ]"; 226 break; 227 default: 228 status = "---"; 229 break; 230 } 231 out.println(String.format("%s %s", status, oi.getOverlayIdentifier())); 232 } 233 runEnableDisable(final boolean enable)234 private int runEnableDisable(final boolean enable) throws RemoteException { 235 final PrintWriter err = getErrPrintWriter(); 236 237 int userId = UserHandle.USER_SYSTEM; 238 String opt; 239 while ((opt = getNextOption()) != null) { 240 switch (opt) { 241 case "--user": 242 userId = UserHandle.parseUserArg(getNextArgRequired()); 243 break; 244 default: 245 err.println("Error: Unknown option: " + opt); 246 return 1; 247 } 248 } 249 250 final OverlayIdentifier overlay = OverlayIdentifier.fromString(getNextArgRequired()); 251 mInterface.commit(new OverlayManagerTransaction.Builder() 252 .setEnabled(overlay, enable, userId) 253 .build()); 254 return 0; 255 } 256 runPartitionOrder()257 private int runPartitionOrder() throws RemoteException { 258 final PrintWriter out = getOutPrintWriter(); 259 out.println("Partition order (low to high priority): " + mInterface.getPartitionOrder()); 260 out.println("Established by " + (mInterface.isDefaultPartitionOrder() ? "default" 261 : PARTITION_ORDER_FILE_PATH)); 262 return 0; 263 } 264 runFabricate()265 private int runFabricate() throws RemoteException { 266 final PrintWriter err = getErrPrintWriter(); 267 if (Binder.getCallingUid() != Process.ROOT_UID) { 268 err.println("Error: must be root to fabricate overlays through the shell"); 269 return 1; 270 } 271 272 int userId = UserHandle.USER_SYSTEM; 273 String targetPackage = ""; 274 String targetOverlayable = ""; 275 String name = ""; 276 String filename = null; 277 String opt; 278 String config = null; 279 while ((opt = getNextOption()) != null) { 280 switch (opt) { 281 case "--user": 282 userId = UserHandle.parseUserArg(getNextArgRequired()); 283 break; 284 case "--target": 285 targetPackage = getNextArgRequired(); 286 break; 287 case "--target-name": 288 targetOverlayable = getNextArgRequired(); 289 break; 290 case "--name": 291 name = getNextArgRequired(); 292 break; 293 case "--file": 294 filename = getNextArgRequired(); 295 break; 296 case "--config": 297 config = getNextArgRequired(); 298 break; 299 default: 300 err.println("Error: Unknown option: " + opt); 301 return 1; 302 } 303 } 304 305 if (name.isEmpty()) { 306 err.println("Error: Missing required arg '--name'"); 307 return 1; 308 } 309 310 if (targetPackage.isEmpty()) { 311 err.println("Error: Missing required arg '--target'"); 312 return 1; 313 } 314 if (filename != null && getRemainingArgsCount() > 0) { 315 err.println( 316 "Error: When passing --file don't pass resource name, type, and value as well"); 317 return 1; 318 } 319 final String overlayPackageName = "com.android.shell"; 320 FabricatedOverlay overlay = new FabricatedOverlay(name, targetPackage); 321 overlay.setTargetOverlayable(targetOverlayable); 322 overlay.setOwningPackage(overlayPackageName); 323 if (filename != null) { 324 int result = addOverlayValuesFromXml(overlay, targetPackage, filename); 325 if (result != 0) { 326 return result; 327 } 328 } else { 329 final String resourceName = getNextArgRequired(); 330 final String typeStr = getNextArgRequired(); 331 final String strData = String.join(" ", peekRemainingArgs()); 332 if (addOverlayValue(overlay, resourceName, typeStr, strData, config) != 0) { 333 return 1; 334 } 335 } 336 337 mInterface.commit(new OverlayManagerTransaction.Builder() 338 .registerFabricatedOverlay(overlay).build()); 339 return 0; 340 } 341 addOverlayValuesFromXml( FabricatedOverlay overlay, String targetPackage, String filename)342 private int addOverlayValuesFromXml( 343 FabricatedOverlay overlay, String targetPackage, String filename) { 344 final PrintWriter err = getErrPrintWriter(); 345 File file = new File(filename); 346 if (!file.exists()) { 347 err.println("Error: File does not exist"); 348 return 1; 349 } 350 if (!file.canRead()) { 351 err.println("Error: File is unreadable"); 352 return 1; 353 } 354 try (FileInputStream fis = new FileInputStream(file)) { 355 TypedXmlPullParser parser = Xml.resolvePullParser(fis); 356 int type; 357 while ((type = parser.next()) != XmlPullParser.START_TAG 358 && type != XmlPullParser.END_DOCUMENT) { 359 continue; 360 } 361 parser.require(XmlPullParser.START_TAG, null, "overlay"); 362 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { 363 if (type == XmlPullParser.START_TAG) { 364 String tagName = parser.getName(); 365 if (!tagName.equals("item")) { 366 err.println(TextUtils.formatSimple("Error: Unexpected tag: %s at line %d", 367 tagName, parser.getLineNumber())); 368 } else if (!parser.isEmptyElementTag()) { 369 err.println("Error: item tag must be empty"); 370 return 1; 371 } else { 372 String target = parser.getAttributeValue(null, "target"); 373 if (TextUtils.isEmpty(target)) { 374 err.println( 375 "Error: target name missing at line " + parser.getLineNumber()); 376 return 1; 377 } 378 int index = target.indexOf('/'); 379 if (index < 0) { 380 err.println("Error: target malformed, missing '/' at line " 381 + parser.getLineNumber()); 382 return 1; 383 } 384 String overlayType = target.substring(0, index); 385 String value = parser.getAttributeValue(null, "value"); 386 if (TextUtils.isEmpty(value)) { 387 err.println("Error: value missing at line " + parser.getLineNumber()); 388 return 1; 389 } 390 String config = parser.getAttributeValue(null, "config"); 391 if (addOverlayValue(overlay, targetPackage + ':' + target, 392 overlayType, value, config) != 0) { 393 return 1; 394 } 395 } 396 } 397 } 398 } catch (IOException e) { 399 e.printStackTrace(); 400 return 1; 401 } catch (XmlPullParserException e) { 402 e.printStackTrace(); 403 return 1; 404 } 405 return 0; 406 } 407 addOverlayValue(FabricatedOverlay overlay, String resourceName, String typeString, String valueString, String configuration)408 private int addOverlayValue(FabricatedOverlay overlay, String resourceName, String typeString, 409 String valueString, String configuration) { 410 final int type; 411 typeString = typeString.toLowerCase(Locale.getDefault()); 412 if (TYPE_MAP.containsKey(typeString)) { 413 type = TYPE_MAP.get(typeString); 414 } else { 415 if (typeString.startsWith("0x")) { 416 type = Integer.parseUnsignedInt(typeString.substring(2), 16); 417 } else { 418 type = Integer.parseUnsignedInt(typeString); 419 } 420 } 421 if (type == TypedValue.TYPE_STRING) { 422 overlay.setResourceValue(resourceName, type, valueString, configuration); 423 } else if (type < 0) { 424 ParcelFileDescriptor pfd = openFileForSystem(valueString, "r"); 425 if (valueString.endsWith(".9.png")) { 426 overlay.setNinePatchResourceValue(resourceName, pfd, configuration); 427 } else { 428 overlay.setResourceValue(resourceName, pfd, configuration); 429 } 430 } else { 431 final int intData; 432 if (valueString.startsWith("0x")) { 433 intData = Integer.parseUnsignedInt(valueString.substring(2), 16); 434 } else { 435 intData = Integer.parseUnsignedInt(valueString); 436 } 437 overlay.setResourceValue(resourceName, type, intData, configuration); 438 } 439 return 0; 440 } 441 runEnableExclusive()442 private int runEnableExclusive() throws RemoteException { 443 final PrintWriter err = getErrPrintWriter(); 444 445 int userId = UserHandle.USER_SYSTEM; 446 boolean inCategory = false; 447 String opt; 448 while ((opt = getNextOption()) != null) { 449 switch (opt) { 450 case "--user": 451 userId = UserHandle.parseUserArg(getNextArgRequired()); 452 break; 453 case "--category": 454 inCategory = true; 455 break; 456 default: 457 err.println("Error: Unknown option: " + opt); 458 return 1; 459 } 460 } 461 final String overlay = getNextArgRequired(); 462 if (inCategory) { 463 return mInterface.setEnabledExclusiveInCategory(overlay, userId) ? 0 : 1; 464 } else { 465 return mInterface.setEnabledExclusive(overlay, true, userId) ? 0 : 1; 466 } 467 } 468 runSetPriority()469 private int runSetPriority() throws RemoteException { 470 final PrintWriter err = getErrPrintWriter(); 471 472 int userId = UserHandle.USER_SYSTEM; 473 String opt; 474 while ((opt = getNextOption()) != null) { 475 switch (opt) { 476 case "--user": 477 userId = UserHandle.parseUserArg(getNextArgRequired()); 478 break; 479 default: 480 err.println("Error: Unknown option: " + opt); 481 return 1; 482 } 483 } 484 485 final String packageName = getNextArgRequired(); 486 final String newParentPackageName = getNextArgRequired(); 487 488 if ("highest".equals(newParentPackageName)) { 489 return mInterface.setHighestPriority(packageName, userId) ? 0 : 1; 490 } else if ("lowest".equals(newParentPackageName)) { 491 return mInterface.setLowestPriority(packageName, userId) ? 0 : 1; 492 } else { 493 return mInterface.setPriority(packageName, newParentPackageName, userId) ? 0 : 1; 494 } 495 } 496 runLookup()497 private int runLookup() throws RemoteException { 498 final PrintWriter out = getOutPrintWriter(); 499 final PrintWriter err = getErrPrintWriter(); 500 501 int userId = UserHandle.USER_SYSTEM; 502 boolean verbose = false; 503 String opt; 504 while ((opt = getNextOption()) != null) { 505 switch (opt) { 506 case "--user": 507 userId = UserHandle.parseUserArg(getNextArgRequired()); 508 break; 509 case "--verbose": 510 verbose = true; 511 break; 512 default: 513 err.println("Error: Unknown option: " + opt); 514 return 1; 515 } 516 } 517 518 final String packageToLoad = getNextArgRequired(); 519 520 final String fullyQualifiedResourceName = getNextArgRequired(); // package:type/name 521 final Pattern regex = Pattern.compile("(.*?):(.*?)/(.*?)"); 522 final Matcher matcher = regex.matcher(fullyQualifiedResourceName); 523 if (!matcher.matches()) { 524 err.println("Error: bad resource name, doesn't match package:type/name"); 525 return 1; 526 } 527 528 final Resources res; 529 try { 530 res = mContext 531 .createContextAsUser(UserHandle.of(userId), /* flags */ 0) 532 .getPackageManager() 533 .getResourcesForApplication(packageToLoad); 534 } catch (PackageManager.NameNotFoundException e) { 535 err.println(String.format("Error: failed to get resources for package %s for user %d", 536 packageToLoad, userId)); 537 return 1; 538 } 539 final AssetManager assets = res.getAssets(); 540 try { 541 assets.setResourceResolutionLoggingEnabled(true); 542 543 // first try as non-complex type ... 544 try { 545 final TypedValue value = new TypedValue(); 546 res.getValue(fullyQualifiedResourceName, value, false /* resolveRefs */); 547 final CharSequence valueString = value.coerceToString(); 548 final String resolution = assets.getLastResourceResolution(); 549 550 res.getValue(fullyQualifiedResourceName, value, true /* resolveRefs */); 551 final CharSequence resolvedString = value.coerceToString(); 552 553 if (verbose) { 554 out.println(resolution); 555 } 556 557 if (valueString.equals(resolvedString)) { 558 out.println(valueString); 559 } else { 560 out.println(valueString + " -> " + resolvedString); 561 } 562 return 0; 563 } catch (Resources.NotFoundException e) { 564 // this is ok, resource could still be a complex type 565 } 566 567 // ... then try as complex type 568 try { 569 570 final String pkg = matcher.group(1); 571 final String type = matcher.group(2); 572 final String name = matcher.group(3); 573 final int resid = res.getIdentifier(name, type, pkg); 574 if (resid == 0) { 575 throw new Resources.NotFoundException(); 576 } 577 final TypedArray array = res.obtainTypedArray(resid); 578 if (verbose) { 579 out.println(assets.getLastResourceResolution()); 580 } 581 TypedValue tv = new TypedValue(); 582 for (int i = 0; i < array.length(); i++) { 583 array.getValue(i, tv); 584 out.println(tv.coerceToString()); 585 } 586 array.recycle(); 587 return 0; 588 } catch (Resources.NotFoundException e) { 589 // give up 590 err.println("Error: failed to get the resource " + fullyQualifiedResourceName); 591 return 1; 592 } 593 } finally { 594 assets.setResourceResolutionLoggingEnabled(false); 595 } 596 } 597 } 598