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