• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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