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.IOverlayManager; 23 import android.content.om.OverlayInfo; 24 import android.content.pm.PackageManager; 25 import android.content.res.AssetManager; 26 import android.content.res.Resources; 27 import android.content.res.TypedArray; 28 import android.os.RemoteException; 29 import android.os.ShellCommand; 30 import android.os.UserHandle; 31 import android.util.TypedValue; 32 33 import java.io.PrintWriter; 34 import java.util.List; 35 import java.util.Map; 36 import java.util.regex.Matcher; 37 import java.util.regex.Pattern; 38 39 /** 40 * Implementation of 'cmd overlay' commands. 41 * 42 * This class provides an interface to the OverlayManagerService via adb. 43 * Intended only for manual debugging. Execute 'adb exec-out cmd overlay help' 44 * for a list of available commands. 45 */ 46 final class OverlayManagerShellCommand extends ShellCommand { 47 private final Context mContext; 48 private final IOverlayManager mInterface; 49 OverlayManagerShellCommand(@onNull final Context ctx, @NonNull final IOverlayManager iom)50 OverlayManagerShellCommand(@NonNull final Context ctx, @NonNull final IOverlayManager iom) { 51 mContext = ctx; 52 mInterface = iom; 53 } 54 55 @Override onCommand(@ullable final String cmd)56 public int onCommand(@Nullable final String cmd) { 57 if (cmd == null) { 58 return handleDefaultCommands(cmd); 59 } 60 final PrintWriter err = getErrPrintWriter(); 61 try { 62 switch (cmd) { 63 case "list": 64 return runList(); 65 case "enable": 66 return runEnableDisable(true); 67 case "disable": 68 return runEnableDisable(false); 69 case "enable-exclusive": 70 return runEnableExclusive(); 71 case "set-priority": 72 return runSetPriority(); 73 case "lookup": 74 return runLookup(); 75 default: 76 return handleDefaultCommands(cmd); 77 } 78 } catch (IllegalArgumentException e) { 79 err.println("Error: " + e.getMessage()); 80 } catch (RemoteException e) { 81 err.println("Remote exception: " + e); 82 } 83 return -1; 84 } 85 86 @Override onHelp()87 public void onHelp() { 88 final PrintWriter out = getOutPrintWriter(); 89 out.println("Overlay manager (overlay) commands:"); 90 out.println(" help"); 91 out.println(" Print this help text."); 92 out.println(" dump [--verbose] [--user USER_ID] [[FIELD] PACKAGE]"); 93 out.println(" Print debugging information about the overlay manager."); 94 out.println(" With optional parameter PACKAGE, limit output to the specified"); 95 out.println(" package. With optional parameter FIELD, limit output to"); 96 out.println(" the value of that SettingsItem field. Field names are"); 97 out.println(" case insensitive and out.println the m prefix can be omitted,"); 98 out.println(" so the following are equivalent: mState, mstate, State, state."); 99 out.println(" list [--user USER_ID] [PACKAGE]"); 100 out.println(" Print information about target and overlay packages."); 101 out.println(" Overlay packages are printed in priority order. With optional"); 102 out.println(" parameter PACKAGE, limit output to the specified package."); 103 out.println(" enable [--user USER_ID] PACKAGE"); 104 out.println(" Enable overlay package PACKAGE."); 105 out.println(" disable [--user USER_ID] PACKAGE"); 106 out.println(" Disable overlay package PACKAGE."); 107 out.println(" enable-exclusive [--user USER_ID] [--category] PACKAGE"); 108 out.println(" Enable overlay package PACKAGE and disable all other overlays for"); 109 out.println(" its target package. If the --category option is given, only disables"); 110 out.println(" other overlays in the same category."); 111 out.println(" set-priority [--user USER_ID] PACKAGE PARENT|lowest|highest"); 112 out.println(" Change the priority of the overlay PACKAGE to be just higher than"); 113 out.println(" the priority of PACKAGE_PARENT If PARENT is the special keyword"); 114 out.println(" 'lowest', change priority of PACKAGE to the lowest priority."); 115 out.println(" If PARENT is the special keyword 'highest', change priority of"); 116 out.println(" PACKAGE to the highest priority."); 117 out.println(" lookup [--verbose] PACKAGE-TO-LOAD PACKAGE:TYPE/NAME"); 118 out.println(" Load a package and print the value of a given resource"); 119 out.println(" applying the current configuration and enabled overlays."); 120 out.println(" For a more fine-grained alernative, use 'idmap2 lookup'."); 121 } 122 runList()123 private int runList() throws RemoteException { 124 final PrintWriter out = getOutPrintWriter(); 125 final PrintWriter err = getErrPrintWriter(); 126 127 int userId = UserHandle.USER_SYSTEM; 128 String opt; 129 while ((opt = getNextOption()) != null) { 130 switch (opt) { 131 case "--user": 132 userId = UserHandle.parseUserArg(getNextArgRequired()); 133 break; 134 default: 135 err.println("Error: Unknown option: " + opt); 136 return 1; 137 } 138 } 139 140 final String packageName = getNextArg(); 141 if (packageName != null) { 142 List<OverlayInfo> overlaysForTarget = mInterface.getOverlayInfosForTarget( 143 packageName, userId); 144 145 // If the package is not targeted by any overlays, check if the package is an overlay. 146 if (overlaysForTarget.isEmpty()) { 147 final OverlayInfo info = mInterface.getOverlayInfo(packageName, userId); 148 if (info != null) { 149 printListOverlay(out, info); 150 } 151 return 0; 152 } 153 154 out.println(packageName); 155 156 // Print the overlays for the target. 157 final int n = overlaysForTarget.size(); 158 for (int i = 0; i < n; i++) { 159 printListOverlay(out, overlaysForTarget.get(i)); 160 } 161 162 return 0; 163 } 164 165 // Print all overlays grouped by target package name. 166 final Map<String, List<OverlayInfo>> allOverlays = mInterface.getAllOverlays(userId); 167 for (final String targetPackageName : allOverlays.keySet()) { 168 out.println(targetPackageName); 169 170 List<OverlayInfo> overlaysForTarget = allOverlays.get(targetPackageName); 171 final int n = overlaysForTarget.size(); 172 for (int i = 0; i < n; i++) { 173 printListOverlay(out, overlaysForTarget.get(i)); 174 } 175 out.println(); 176 } 177 178 return 0; 179 } 180 printListOverlay(PrintWriter out, OverlayInfo oi)181 private void printListOverlay(PrintWriter out, OverlayInfo oi) { 182 String status; 183 switch (oi.state) { 184 case OverlayInfo.STATE_ENABLED_IMMUTABLE: 185 case OverlayInfo.STATE_ENABLED: 186 status = "[x]"; 187 break; 188 case OverlayInfo.STATE_DISABLED: 189 status = "[ ]"; 190 break; 191 default: 192 status = "---"; 193 break; 194 } 195 out.println(String.format("%s %s", status, oi.packageName)); 196 } 197 runEnableDisable(final boolean enable)198 private int runEnableDisable(final boolean enable) throws RemoteException { 199 final PrintWriter err = getErrPrintWriter(); 200 201 int userId = UserHandle.USER_SYSTEM; 202 String opt; 203 while ((opt = getNextOption()) != null) { 204 switch (opt) { 205 case "--user": 206 userId = UserHandle.parseUserArg(getNextArgRequired()); 207 break; 208 default: 209 err.println("Error: Unknown option: " + opt); 210 return 1; 211 } 212 } 213 214 final String packageName = getNextArgRequired(); 215 return mInterface.setEnabled(packageName, enable, userId) ? 0 : 1; 216 } 217 runEnableExclusive()218 private int runEnableExclusive() throws RemoteException { 219 final PrintWriter err = getErrPrintWriter(); 220 221 int userId = UserHandle.USER_SYSTEM; 222 boolean inCategory = false; 223 String opt; 224 while ((opt = getNextOption()) != null) { 225 switch (opt) { 226 case "--user": 227 userId = UserHandle.parseUserArg(getNextArgRequired()); 228 break; 229 case "--category": 230 inCategory = true; 231 break; 232 default: 233 err.println("Error: Unknown option: " + opt); 234 return 1; 235 } 236 } 237 final String overlay = getNextArgRequired(); 238 if (inCategory) { 239 return mInterface.setEnabledExclusiveInCategory(overlay, userId) ? 0 : 1; 240 } else { 241 return mInterface.setEnabledExclusive(overlay, true, userId) ? 0 : 1; 242 } 243 } 244 runSetPriority()245 private int runSetPriority() throws RemoteException { 246 final PrintWriter err = getErrPrintWriter(); 247 248 int userId = UserHandle.USER_SYSTEM; 249 String opt; 250 while ((opt = getNextOption()) != null) { 251 switch (opt) { 252 case "--user": 253 userId = UserHandle.parseUserArg(getNextArgRequired()); 254 break; 255 default: 256 err.println("Error: Unknown option: " + opt); 257 return 1; 258 } 259 } 260 261 final String packageName = getNextArgRequired(); 262 final String newParentPackageName = getNextArgRequired(); 263 264 if ("highest".equals(newParentPackageName)) { 265 return mInterface.setHighestPriority(packageName, userId) ? 0 : 1; 266 } else if ("lowest".equals(newParentPackageName)) { 267 return mInterface.setLowestPriority(packageName, userId) ? 0 : 1; 268 } else { 269 return mInterface.setPriority(packageName, newParentPackageName, userId) ? 0 : 1; 270 } 271 } 272 runLookup()273 private int runLookup() throws RemoteException { 274 final PrintWriter out = getOutPrintWriter(); 275 final PrintWriter err = getErrPrintWriter(); 276 277 final boolean verbose = "--verbose".equals(getNextOption()); 278 279 final String packageToLoad = getNextArgRequired(); 280 281 final String fullyQualifiedResourceName = getNextArgRequired(); // package:type/name 282 final Pattern regex = Pattern.compile("(.*?):(.*?)/(.*?)"); 283 final Matcher matcher = regex.matcher(fullyQualifiedResourceName); 284 if (!matcher.matches()) { 285 err.println("Error: bad resource name, doesn't match package:type/name"); 286 return 1; 287 } 288 289 final PackageManager pm = mContext.getPackageManager(); 290 if (pm == null) { 291 err.println("Error: failed to get package manager"); 292 return 1; 293 } 294 295 final Resources res; 296 try { 297 res = pm.getResourcesForApplication(packageToLoad); 298 } catch (PackageManager.NameNotFoundException e) { 299 err.println("Error: failed to get resources for package " + packageToLoad); 300 return 1; 301 } 302 final AssetManager assets = res.getAssets(); 303 try { 304 assets.setResourceResolutionLoggingEnabled(true); 305 306 // first try as non-complex type ... 307 try { 308 final TypedValue value = new TypedValue(); 309 res.getValue(fullyQualifiedResourceName, value, false /* resolveRefs */); 310 final CharSequence valueString = value.coerceToString(); 311 final String resolution = assets.getLastResourceResolution(); 312 313 res.getValue(fullyQualifiedResourceName, value, true /* resolveRefs */); 314 final CharSequence resolvedString = value.coerceToString(); 315 316 if (verbose) { 317 out.println(resolution); 318 } 319 320 if (valueString.equals(resolvedString)) { 321 out.println(valueString); 322 } else { 323 out.println(valueString + " -> " + resolvedString); 324 } 325 return 0; 326 } catch (Resources.NotFoundException e) { 327 // this is ok, resource could still be a complex type 328 } 329 330 // ... then try as complex type 331 try { 332 333 final String pkg = matcher.group(1); 334 final String type = matcher.group(2); 335 final String name = matcher.group(3); 336 final int resid = res.getIdentifier(name, type, pkg); 337 if (resid == 0) { 338 throw new Resources.NotFoundException(); 339 } 340 final TypedArray array = res.obtainTypedArray(resid); 341 if (verbose) { 342 out.println(assets.getLastResourceResolution()); 343 } 344 TypedValue tv = new TypedValue(); 345 for (int i = 0; i < array.length(); i++) { 346 array.getValue(i, tv); 347 out.println(tv.coerceToString()); 348 } 349 array.recycle(); 350 return 0; 351 } catch (Resources.NotFoundException e) { 352 // give up 353 err.println("Error: failed to get the resource " + fullyQualifiedResourceName); 354 return 1; 355 } 356 } finally { 357 assets.setResourceResolutionLoggingEnabled(false); 358 } 359 } 360 } 361