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