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.Process; 33 import android.os.RemoteException; 34 import android.os.ShellCommand; 35 import android.os.UserHandle; 36 import android.util.TypedValue; 37 38 import java.io.PrintWriter; 39 import java.util.List; 40 import java.util.Map; 41 import java.util.regex.Matcher; 42 import java.util.regex.Pattern; 43 44 /** 45 * Implementation of 'cmd overlay' commands. 46 * 47 * This class provides an interface to the OverlayManagerService via adb. 48 * Intended only for manual debugging. Execute 'adb exec-out cmd overlay help' 49 * for a list of available commands. 50 */ 51 final class OverlayManagerShellCommand extends ShellCommand { 52 private final Context mContext; 53 private final IOverlayManager mInterface; 54 OverlayManagerShellCommand(@onNull final Context ctx, @NonNull final IOverlayManager iom)55 OverlayManagerShellCommand(@NonNull final Context ctx, @NonNull final IOverlayManager iom) { 56 mContext = ctx; 57 mInterface = iom; 58 } 59 60 @Override onCommand(@ullable final String cmd)61 public int onCommand(@Nullable final String cmd) { 62 if (cmd == null) { 63 return handleDefaultCommands(cmd); 64 } 65 final PrintWriter err = getErrPrintWriter(); 66 try { 67 switch (cmd) { 68 case "list": 69 return runList(); 70 case "enable": 71 return runEnableDisable(true); 72 case "disable": 73 return runEnableDisable(false); 74 case "enable-exclusive": 75 return runEnableExclusive(); 76 case "set-priority": 77 return runSetPriority(); 78 case "lookup": 79 return runLookup(); 80 case "fabricate": 81 return runFabricate(); 82 default: 83 return handleDefaultCommands(cmd); 84 } 85 } catch (IllegalArgumentException e) { 86 err.println("Error: " + e.getMessage()); 87 } catch (RemoteException e) { 88 err.println("Remote exception: " + e); 89 } 90 return -1; 91 } 92 93 @Override onHelp()94 public void onHelp() { 95 final PrintWriter out = getOutPrintWriter(); 96 out.println("Overlay manager (overlay) commands:"); 97 out.println(" help"); 98 out.println(" Print this help text."); 99 out.println(" dump [--verbose] [--user USER_ID] [[FIELD] PACKAGE[:NAME]]"); 100 out.println(" Print debugging information about the overlay manager."); 101 out.println(" With optional parameters PACKAGE and NAME, limit output to the specified"); 102 out.println(" overlay or target. With optional parameter FIELD, limit output to"); 103 out.println(" the corresponding SettingsItem field. Field names are all lower case"); 104 out.println(" and omit the m prefix, i.e. 'userid' for SettingsItem.mUserId."); 105 out.println(" list [--user USER_ID] [PACKAGE[:NAME]]"); 106 out.println(" Print information about target and overlay packages."); 107 out.println(" Overlay packages are printed in priority order. With optional"); 108 out.println(" parameters PACKAGE and NAME, limit output to the specified overlay or"); 109 out.println(" target."); 110 out.println(" enable [--user USER_ID] PACKAGE[:NAME]"); 111 out.println(" Enable overlay within or owned by PACKAGE with optional unique NAME."); 112 out.println(" disable [--user USER_ID] PACKAGE[:NAME]"); 113 out.println(" Disable overlay within or owned by PACKAGE with optional unique NAME."); 114 out.println(" enable-exclusive [--user USER_ID] [--category] PACKAGE"); 115 out.println(" Enable overlay within or owned by PACKAGE and disable all other overlays"); 116 out.println(" for its target package. If the --category option is given, only disables"); 117 out.println(" other overlays in the same category."); 118 out.println(" set-priority [--user USER_ID] PACKAGE PARENT|lowest|highest"); 119 out.println(" Change the priority of the overlay to be just higher than"); 120 out.println(" the priority of PARENT If PARENT is the special keyword"); 121 out.println(" 'lowest', change priority of PACKAGE to the lowest priority."); 122 out.println(" If PARENT is the special keyword 'highest', change priority of"); 123 out.println(" PACKAGE to the highest priority."); 124 out.println(" lookup [--user USER_ID] [--verbose] PACKAGE-TO-LOAD PACKAGE:TYPE/NAME"); 125 out.println(" Load a package and print the value of a given resource"); 126 out.println(" applying the current configuration and enabled overlays."); 127 out.println(" For a more fine-grained alternative, use 'idmap2 lookup'."); 128 out.println(" fabricate [--user USER_ID] [--target-name OVERLAYABLE] --target PACKAGE"); 129 out.println(" --name NAME PACKAGE:TYPE/NAME ENCODED-TYPE-ID ENCODED-VALUE"); 130 out.println(" Create an overlay from a single resource. Caller must be root. Example:"); 131 out.println(" fabricate --target android --name LighterGray \\"); 132 out.println(" android:color/lighter_gray 0x1c 0xffeeeeee"); 133 } 134 runList()135 private int runList() throws RemoteException { 136 final PrintWriter out = getOutPrintWriter(); 137 final PrintWriter err = getErrPrintWriter(); 138 139 int userId = UserHandle.USER_SYSTEM; 140 String opt; 141 while ((opt = getNextOption()) != null) { 142 switch (opt) { 143 case "--user": 144 userId = UserHandle.parseUserArg(getNextArgRequired()); 145 break; 146 default: 147 err.println("Error: Unknown option: " + opt); 148 return 1; 149 } 150 } 151 152 final String packageName = getNextArg(); 153 if (packageName != null) { 154 List<OverlayInfo> overlaysForTarget = mInterface.getOverlayInfosForTarget( 155 packageName, userId); 156 157 // If the package is not targeted by any overlays, check if the package is an overlay. 158 if (overlaysForTarget.isEmpty()) { 159 final OverlayInfo info = mInterface.getOverlayInfo(packageName, userId); 160 if (info != null) { 161 printListOverlay(out, info); 162 } 163 return 0; 164 } 165 166 out.println(packageName); 167 168 // Print the overlays for the target. 169 final int n = overlaysForTarget.size(); 170 for (int i = 0; i < n; i++) { 171 printListOverlay(out, overlaysForTarget.get(i)); 172 } 173 174 return 0; 175 } 176 177 // Print all overlays grouped by target package name. 178 final Map<String, List<OverlayInfo>> allOverlays = mInterface.getAllOverlays(userId); 179 for (final String targetPackageName : allOverlays.keySet()) { 180 out.println(targetPackageName); 181 182 List<OverlayInfo> overlaysForTarget = allOverlays.get(targetPackageName); 183 final int n = overlaysForTarget.size(); 184 for (int i = 0; i < n; i++) { 185 printListOverlay(out, overlaysForTarget.get(i)); 186 } 187 out.println(); 188 } 189 190 return 0; 191 } 192 printListOverlay(PrintWriter out, OverlayInfo oi)193 private void printListOverlay(PrintWriter out, OverlayInfo oi) { 194 String status; 195 switch (oi.state) { 196 case OverlayInfo.STATE_ENABLED_IMMUTABLE: 197 case OverlayInfo.STATE_ENABLED: 198 status = "[x]"; 199 break; 200 case OverlayInfo.STATE_DISABLED: 201 status = "[ ]"; 202 break; 203 default: 204 status = "---"; 205 break; 206 } 207 out.println(String.format("%s %s", status, oi.getOverlayIdentifier())); 208 } 209 runEnableDisable(final boolean enable)210 private int runEnableDisable(final boolean enable) throws RemoteException { 211 final PrintWriter err = getErrPrintWriter(); 212 213 int userId = UserHandle.USER_SYSTEM; 214 String opt; 215 while ((opt = getNextOption()) != null) { 216 switch (opt) { 217 case "--user": 218 userId = UserHandle.parseUserArg(getNextArgRequired()); 219 break; 220 default: 221 err.println("Error: Unknown option: " + opt); 222 return 1; 223 } 224 } 225 226 final OverlayIdentifier overlay = OverlayIdentifier.fromString(getNextArgRequired()); 227 mInterface.commit(new OverlayManagerTransaction.Builder() 228 .setEnabled(overlay, enable, userId) 229 .build()); 230 return 0; 231 } 232 runFabricate()233 private int runFabricate() throws RemoteException { 234 final PrintWriter err = getErrPrintWriter(); 235 if (Binder.getCallingUid() != Process.ROOT_UID) { 236 err.println("Error: must be root to fabricate overlays through the shell"); 237 return 1; 238 } 239 240 int userId = UserHandle.USER_SYSTEM; 241 String targetPackage = ""; 242 String targetOverlayable = ""; 243 String name = ""; 244 String opt; 245 while ((opt = getNextOption()) != null) { 246 switch (opt) { 247 case "--user": 248 userId = UserHandle.parseUserArg(getNextArgRequired()); 249 break; 250 case "--target": 251 targetPackage = getNextArgRequired(); 252 break; 253 case "--target-name": 254 targetOverlayable = getNextArgRequired(); 255 break; 256 case "--name": 257 name = getNextArgRequired(); 258 break; 259 default: 260 err.println("Error: Unknown option: " + opt); 261 return 1; 262 } 263 } 264 265 if (name.isEmpty()) { 266 err.println("Error: Missing required arg '--name'"); 267 return 1; 268 } 269 270 if (targetPackage.isEmpty()) { 271 err.println("Error: Missing required arg '--target'"); 272 return 1; 273 } 274 275 final String resourceName = getNextArgRequired(); 276 final String typeStr = getNextArgRequired(); 277 final int type; 278 if (typeStr.startsWith("0x")) { 279 type = Integer.parseUnsignedInt(typeStr.substring(2), 16); 280 } else { 281 type = Integer.parseUnsignedInt(typeStr); 282 } 283 final String dataStr = getNextArgRequired(); 284 final int data; 285 if (dataStr.startsWith("0x")) { 286 data = Integer.parseUnsignedInt(dataStr.substring(2), 16); 287 } else { 288 data = Integer.parseUnsignedInt(dataStr); 289 } 290 291 final PackageManager pm = mContext.getPackageManager(); 292 if (pm == null) { 293 err.println("Error: failed to get package manager"); 294 return 1; 295 } 296 297 final String overlayPackageName = "com.android.shell"; 298 final FabricatedOverlay overlay = new FabricatedOverlay.Builder( 299 overlayPackageName, name, targetPackage) 300 .setTargetOverlayable(targetOverlayable) 301 .setResourceValue(resourceName, type, data) 302 .build(); 303 304 mInterface.commit(new OverlayManagerTransaction.Builder() 305 .registerFabricatedOverlay(overlay) 306 .build()); 307 return 0; 308 } 309 runEnableExclusive()310 private int runEnableExclusive() throws RemoteException { 311 final PrintWriter err = getErrPrintWriter(); 312 313 int userId = UserHandle.USER_SYSTEM; 314 boolean inCategory = false; 315 String opt; 316 while ((opt = getNextOption()) != null) { 317 switch (opt) { 318 case "--user": 319 userId = UserHandle.parseUserArg(getNextArgRequired()); 320 break; 321 case "--category": 322 inCategory = true; 323 break; 324 default: 325 err.println("Error: Unknown option: " + opt); 326 return 1; 327 } 328 } 329 final String overlay = getNextArgRequired(); 330 if (inCategory) { 331 return mInterface.setEnabledExclusiveInCategory(overlay, userId) ? 0 : 1; 332 } else { 333 return mInterface.setEnabledExclusive(overlay, true, userId) ? 0 : 1; 334 } 335 } 336 runSetPriority()337 private int runSetPriority() throws RemoteException { 338 final PrintWriter err = getErrPrintWriter(); 339 340 int userId = UserHandle.USER_SYSTEM; 341 String opt; 342 while ((opt = getNextOption()) != null) { 343 switch (opt) { 344 case "--user": 345 userId = UserHandle.parseUserArg(getNextArgRequired()); 346 break; 347 default: 348 err.println("Error: Unknown option: " + opt); 349 return 1; 350 } 351 } 352 353 final String packageName = getNextArgRequired(); 354 final String newParentPackageName = getNextArgRequired(); 355 356 if ("highest".equals(newParentPackageName)) { 357 return mInterface.setHighestPriority(packageName, userId) ? 0 : 1; 358 } else if ("lowest".equals(newParentPackageName)) { 359 return mInterface.setLowestPriority(packageName, userId) ? 0 : 1; 360 } else { 361 return mInterface.setPriority(packageName, newParentPackageName, userId) ? 0 : 1; 362 } 363 } 364 runLookup()365 private int runLookup() throws RemoteException { 366 final PrintWriter out = getOutPrintWriter(); 367 final PrintWriter err = getErrPrintWriter(); 368 369 int userId = UserHandle.USER_SYSTEM; 370 boolean verbose = false; 371 String opt; 372 while ((opt = getNextOption()) != null) { 373 switch (opt) { 374 case "--user": 375 userId = UserHandle.parseUserArg(getNextArgRequired()); 376 break; 377 case "--verbose": 378 verbose = true; 379 break; 380 default: 381 err.println("Error: Unknown option: " + opt); 382 return 1; 383 } 384 } 385 386 final String packageToLoad = getNextArgRequired(); 387 388 final String fullyQualifiedResourceName = getNextArgRequired(); // package:type/name 389 final Pattern regex = Pattern.compile("(.*?):(.*?)/(.*?)"); 390 final Matcher matcher = regex.matcher(fullyQualifiedResourceName); 391 if (!matcher.matches()) { 392 err.println("Error: bad resource name, doesn't match package:type/name"); 393 return 1; 394 } 395 396 final Resources res; 397 try { 398 res = mContext 399 .createContextAsUser(UserHandle.of(userId), /* flags */ 0) 400 .getPackageManager() 401 .getResourcesForApplication(packageToLoad); 402 } catch (PackageManager.NameNotFoundException e) { 403 err.println(String.format("Error: failed to get resources for package %s for user %d", 404 packageToLoad, userId)); 405 return 1; 406 } 407 final AssetManager assets = res.getAssets(); 408 try { 409 assets.setResourceResolutionLoggingEnabled(true); 410 411 // first try as non-complex type ... 412 try { 413 final TypedValue value = new TypedValue(); 414 res.getValue(fullyQualifiedResourceName, value, false /* resolveRefs */); 415 final CharSequence valueString = value.coerceToString(); 416 final String resolution = assets.getLastResourceResolution(); 417 418 res.getValue(fullyQualifiedResourceName, value, true /* resolveRefs */); 419 final CharSequence resolvedString = value.coerceToString(); 420 421 if (verbose) { 422 out.println(resolution); 423 } 424 425 if (valueString.equals(resolvedString)) { 426 out.println(valueString); 427 } else { 428 out.println(valueString + " -> " + resolvedString); 429 } 430 return 0; 431 } catch (Resources.NotFoundException e) { 432 // this is ok, resource could still be a complex type 433 } 434 435 // ... then try as complex type 436 try { 437 438 final String pkg = matcher.group(1); 439 final String type = matcher.group(2); 440 final String name = matcher.group(3); 441 final int resid = res.getIdentifier(name, type, pkg); 442 if (resid == 0) { 443 throw new Resources.NotFoundException(); 444 } 445 final TypedArray array = res.obtainTypedArray(resid); 446 if (verbose) { 447 out.println(assets.getLastResourceResolution()); 448 } 449 TypedValue tv = new TypedValue(); 450 for (int i = 0; i < array.length(); i++) { 451 array.getValue(i, tv); 452 out.println(tv.coerceToString()); 453 } 454 array.recycle(); 455 return 0; 456 } catch (Resources.NotFoundException e) { 457 // give up 458 err.println("Error: failed to get the resource " + fullyQualifiedResourceName); 459 return 1; 460 } 461 } finally { 462 assets.setResourceResolutionLoggingEnabled(false); 463 } 464 } 465 } 466